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>()
}
internal class NativeIndexImpl : NativeIndex() {
internal class NativeIndexImpl(val language: Language) : NativeIndex() {
private data class DeclarationID(val usr: String)
@@ -260,7 +260,11 @@ internal class NativeIndexImpl : NativeIndex() {
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 -> {
@@ -288,7 +292,7 @@ internal class NativeIndexImpl : NativeIndex() {
}
fun buildNativeIndexImpl(library: NativeLibrary): NativeIndex {
val result = NativeIndexImpl()
val result = NativeIndexImpl(library.language)
indexDeclarations(library, result)
findMacroConstants(library, result)
return result
@@ -69,7 +69,7 @@ class Parameter(val name: String?, val type: Type)
/**
* 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.
@@ -421,22 +421,20 @@ class StubGenerator(
}
fun getCFunctionParamBinding(type: Type): OutValueBinding {
when (type) {
is PointerType -> {
when (type.pointeeType) {
is Int8Type -> return OutValueBinding(
kotlinType = "String?",
kotlinConv = { name -> "$name?.toCString(memScope).rawPtr" },
memScoped = true,
kotlinJniBridgeType = "NativePtr"
)
}
}
if (treatCFunctionParameterAsString(type)) {
return OutValueBinding(
kotlinType = "String?",
kotlinConv = { name -> "$name?.toCString(memScope).rawPtr" },
memScoped = true,
kotlinJniBridgeType = "NativePtr"
)
}
return getOutValueBinding(type)
}
private fun treatCFunctionParameterAsString(type: Type) = type is PointerType && type.pointeeType is Int8Type
fun getCallbackRetValBinding(type: Type): OutValueBinding {
if (type.unwrapTypedefs() is VoidType) {
return OutValueBinding(
@@ -657,8 +655,15 @@ class StubGenerator(
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) {
val externalParamNames = paramNames.mapIndexed { i: Int, name: String ->
val arguments = paramNames.mapIndexed { i: Int, name: String ->
val binding = paramBindings[i]
val externalParamName: String
@@ -672,7 +677,11 @@ class StubGenerator(
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")
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>) =
"Struct(" +
elementTypes.map { getFfiType(it) }.joinToString(", ") +
@@ -747,13 +778,39 @@ class StubGenerator(
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)
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) {
if (requiresAdapterOnNative(type)) {
if (type.requiresAdapterOnNative()) {
out("object $name : CFunctionType {}")
} else {
val kotlinFunctionType = getKotlinFunctionType(type)
@@ -853,12 +910,41 @@ class StubGenerator(
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() {
require(platform == KotlinPlatform.NATIVE)
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.
*/
@@ -871,10 +957,16 @@ class StubGenerator(
"$name: " + paramBindings[i].kotlinJniBridgeType
}.joinToString(", ")
if (platform == KotlinPlatform.NATIVE) {
out("@SymbolName(\"${func.stubSymbolName}\")")
when (platform) {
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("")
}
block("object externals") {
if (platform == KotlinPlatform.JVM) {
out("init { System.loadLibrary(\"$libName\") }")
}
generateKotlinExternals()
}
private fun generateKotlinExternals() = when (platform) {
KotlinPlatform.JVM -> block("object externals") {
out("init { System.loadLibrary(\"$libName\") }")
functionsToBind.forEach {
try {
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 ->
try {
generateCJniFunction(func)
if (func.requiresCAdapter()) {
generateCJniFunction(func)
}
} catch (e: Throwable) {
System.err.println("Warning: cannot generate C JNI function definition ${func.name}")
}
@@ -1026,17 +1135,36 @@ class StubGenerator(
val paramBindings = paramBindings(func)
val retValBinding = retValBinding(func)
val args =
if (paramBindings.isEmpty())
""
else paramBindings
.map { getCBridgeType(it.kotlinJniBridgeType) }
.mapIndexed { i, type -> "$type ${paramNames[i]}" }
.joinToString(separator = ", ", prefix = ", ")
val parameters = paramBindings
.map { getCBridgeType(it.kotlinJniBridgeType) }
.mapIndexed { i, type -> "$type ${paramNames[i]}" }
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 name = paramNames[i]
val unwrappedType = parameter.type.unwrapTypedefs()
@@ -1050,25 +1178,7 @@ class StubGenerator(
}
}.joinToString(", ")
val callExpr = "${func.name}($params)"
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)"
}
val callExpr = "${func.name}($arguments)"
block(funcDecl) {