Generate C wrapper instead of bridge for all functions. (#3153)

[C Interop] Generate C wrapper instead of bridge for all functions.
This commit is contained in:
Sergey Bogolepov
2019-08-29 11:04:10 +07:00
committed by GitHub
parent 1ee2d8c5d7
commit e3f4d09aeb
6 changed files with 83 additions and 27 deletions
@@ -469,6 +469,11 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
Language.OBJECTIVE_C -> supplier()
}
// We omit `const` qualifier for IntegerType and FloatingType to make `CBridgeGen` simpler.
// See KT-28102.
private fun String.dropConstQualifier() =
substringAfterLast("const ")
private fun convertUnqualifiedPrimitiveType(type: CValue<CXType>): Type = when (type.kind) {
CXTypeKind.CXType_Char_U, CXTypeKind.CXType_Char_S -> {
assert(type.getSize() == 1L)
@@ -479,19 +484,19 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
CXTypeKind.CXType_UInt, CXTypeKind.CXType_ULong, CXTypeKind.CXType_ULongLong -> IntegerType(
size = type.getSize().toInt(),
isSigned = false,
spelling = clang_getTypeSpelling(type).convertAndDispose()
spelling = clang_getTypeSpelling(type).convertAndDispose().dropConstQualifier()
)
CXTypeKind.CXType_SChar, CXTypeKind.CXType_Short,
CXTypeKind.CXType_Int, CXTypeKind.CXType_Long, CXTypeKind.CXType_LongLong -> IntegerType(
size = type.getSize().toInt(),
isSigned = true,
spelling = clang_getTypeSpelling(type).convertAndDispose()
spelling = clang_getTypeSpelling(type).convertAndDispose().dropConstQualifier()
)
CXTypeKind.CXType_Float, CXTypeKind.CXType_Double -> FloatingType(
size = type.getSize().toInt(),
spelling = clang_getTypeSpelling(type).convertAndDispose()
spelling = clang_getTypeSpelling(type).convertAndDispose().dropConstQualifier()
)
CXTypeKind.CXType_Bool -> CBoolType
@@ -264,7 +264,8 @@ open class BoolType: PrimitiveType
object CBoolType : BoolType()
object ObjCBoolType : BoolType()
// We omit `const` qualifier for IntegerType and FloatingType to make `CBridgeGen` simpler.
// See KT-28102.
data class IntegerType(val size: Int, val isSigned: Boolean, val spelling: String) : PrimitiveType
// TODO: floating type is not actually defined entirely by its size.
@@ -6,6 +6,8 @@ package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.gen.jvm.KotlinPlatform
import org.jetbrains.kotlin.native.interop.indexer.ObjCProtocol
import org.jetbrains.kotlin.native.interop.indexer.VoidType
import org.jetbrains.kotlin.native.interop.indexer.unwrapTypedefs
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
class BridgeBuilderResult(
@@ -16,6 +18,8 @@ class BridgeBuilderResult(
val excludedStubs: Set<StubIrElement>
)
private data class CCalleeWrapper(val name: String, val lines: List<String>)
/**
* Generates [NativeBridges] and corresponding function bodies and property accessors.
*/
@@ -68,6 +72,12 @@ class StubIrBridgeBuilder(
private val excludedStubs = mutableSetOf<StubIrElement>()
private val bridgeGeneratingVisitor = object : StubIrVisitor<StubContainer?, Unit> {
private var currentFunctionWrapperId = 0
private fun generateFunctionWrapperName(functionName: String) =
"${functionName}_wrapper${currentFunctionWrapperId++}"
override fun visitClass(element: ClassStub, owner: StubContainer?) {
element.annotations.filterIsInstance<AnnotationStub.ObjC.ExternalClass>().firstOrNull()?.let {
if (it.protocolGetter.isNotEmpty() && element.origin is StubOrigin.ObjCProtocol) {
@@ -104,14 +114,49 @@ class StubIrBridgeBuilder(
val cCallAnnotation = function.annotations.firstIsInstanceOrNull<AnnotationStub.CCall.Symbol>()
?: return
val cCallSymbolName = cCallAnnotation.symbolName
val (wrapperName, wrapperLines) = generateCCalleeWrapper(origin)
simpleBridgeGenerator.insertNativeBridge(
function,
emptyList(),
listOf("extern const void* $cCallSymbolName __asm(${cCallSymbolName.quoteAsKotlinLiteral()});",
"extern const void* $cCallSymbolName = &${origin.function.name};")
listOf(
*wrapperLines.toTypedArray(),
"const void* $cCallSymbolName __asm(${cCallSymbolName.quoteAsKotlinLiteral()});",
"const void* $cCallSymbolName = &$wrapperName;"
)
)
}
/**
* Some functions don't have an address (e.g. macros-based or builtins).
* To solve this problem we generate a wrapper function.
*/
private fun generateCCalleeWrapper(origin: StubOrigin.Function): CCalleeWrapper =
if (origin.function.isVararg) {
CCalleeWrapper(origin.function.name, emptyList())
} else {
val function = origin.function
val wrapperName = generateFunctionWrapperName(function.name)
val returnType = function.returnType.getStringRepresentation()
val parameters = function.parameters.mapIndexed { index, parameter ->
"p$index" to parameter.type.getStringRepresentation()
}
val callExpression = "${function.name}(${parameters.joinToString { it.first }});"
val wrapperBody = if (function.returnType.unwrapTypedefs() is VoidType) {
callExpression
} else {
"return $callExpression"
}
val alwaysInline = "__attribute__((always_inline))"
val lines = listOf(
"$alwaysInline $returnType $wrapperName(${parameters.joinToString { "${it.second} ${it.first}" }}) {",
wrapperBody,
"}"
)
CCalleeWrapper(wrapperName, lines)
}
override fun visitProperty(element: PropertyStub, owner: StubContainer?) {
try {
when (val kind = element.kind) {
@@ -226,6 +271,7 @@ class StubIrBridgeBuilder(
}
private fun generateBridgeBody(function: FunctionStub) {
assert(context.platform == KotlinPlatform.JVM) { "Function ${function.name} was not marked as external." }
assert(function.origin is StubOrigin.Function) { "Can't create bridge for ${function.name}" }
val origin = function.origin as StubOrigin.Function
val bodyGenerator = KotlinCodeBuilder(scope = kotlinFile)
@@ -349,13 +349,15 @@ internal class FunctionStubBuilder(
val annotations: List<AnnotationStub>
val mustBeExternal: Boolean
if (!func.isVararg || platform != KotlinPlatform.NATIVE) {
if (platform == KotlinPlatform.JVM) {
annotations = emptyList()
mustBeExternal = false
} else {
val type = WrapperStubType(KotlinTypes.any.makeNullable())
parameters += FunctionParameterStub("variadicArguments", type, isVararg = true)
annotations = listOf(AnnotationStub.CCall.Symbol(context.generateNextUniqueId("knifunptr_")))
if (func.isVararg) {
val type = WrapperStubType(KotlinTypes.any.makeNullable())
parameters += FunctionParameterStub("variadicArguments", type, isVararg = true)
}
annotations = listOf(AnnotationStub.CCall.Symbol(context.generateNextUniqueId("knifunptr_") + "_${func.name}"))
mustBeExternal = true
}
val functionStub = FunctionStub(
@@ -217,30 +217,31 @@ class StubIrTextEmitter(
override fun visitFunction(element: FunctionStub, owner: StubContainer?) {
if (element in bridgeBuilderResult.excludedStubs) return
val modality = renderMemberModality(element.modality, owner)
val header = run {
val parameters = element.parameters.joinToString(prefix = "(", postfix = ")") { renderFunctionParameter(it) }
val receiver = element.receiver?.let { renderFunctionReceiver(it) + "." } ?: ""
val typeParameters = renderTypeParameters(element.typeParameters)
val modality = renderMemberModality(element.modality, owner)
"${modality}fun$typeParameters $receiver${element.name.asSimpleName()}$parameters: ${renderStubType(element.returnType)}"
}
if (!nativeBridges.isSupported(element)) {
sequenceOf(
annotationForUnableToImport,
"$header = throw UnsupportedOperationException()"
).forEach(out)
return
}
element.annotations.forEach {
out(renderAnnotation(it))
}
val parameters = element.parameters.joinToString(prefix = "(", postfix = ")") { renderFunctionParameter(it) }
val receiver = element.receiver?.let { renderFunctionReceiver(it) + "." } ?: ""
val typeParameters = renderTypeParameters(element.typeParameters)
val header = "${modality}fun$typeParameters $receiver${element.name.asSimpleName()}$parameters: ${renderStubType(element.returnType)}"
when {
element.external -> out("external $header")
element.isOptionalObjCMethod() -> out("$header = optional()")
owner != null && owner.isInterface -> out(header)
else -> if (nativeBridges.isSupported(element)) {
block(header) {
functionBridgeBodies.getValue(element).forEach(out)
}
} else {
sequenceOf(
annotationForUnableToImport,
"$header = throw UnsupportedOperationException()"
).forEach(out)
else -> block(header) {
functionBridgeBodies.getValue(element).forEach(out)
}
}
}
override fun visitProperty(element: PropertyStub, owner: StubContainer?) {
@@ -38,7 +38,8 @@ fun Type.getStringRepresentation(): String = when (this) {
is IntegerType -> this.spelling
is FloatingType -> this.spelling
is PointerType, is ArrayType -> "void*"
is PointerType -> getPointerTypeStringRepresentation(this.pointeeType)
is ArrayType -> getPointerTypeStringRepresentation(this.elemType)
is RecordType -> this.decl.spelling
@@ -58,7 +59,7 @@ fun Type.getStringRepresentation(): String = when (this) {
is ObjCBlockPointer -> "id"
}
else -> throw kotlin.NotImplementedError()
else -> throw NotImplementedError()
}
fun getPointerTypeStringRepresentation(pointee: Type): String =