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:
+8
-3
@@ -469,6 +469,11 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
|
|||||||
Language.OBJECTIVE_C -> supplier()
|
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) {
|
private fun convertUnqualifiedPrimitiveType(type: CValue<CXType>): Type = when (type.kind) {
|
||||||
CXTypeKind.CXType_Char_U, CXTypeKind.CXType_Char_S -> {
|
CXTypeKind.CXType_Char_U, CXTypeKind.CXType_Char_S -> {
|
||||||
assert(type.getSize() == 1L)
|
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(
|
CXTypeKind.CXType_UInt, CXTypeKind.CXType_ULong, CXTypeKind.CXType_ULongLong -> IntegerType(
|
||||||
size = type.getSize().toInt(),
|
size = type.getSize().toInt(),
|
||||||
isSigned = false,
|
isSigned = false,
|
||||||
spelling = clang_getTypeSpelling(type).convertAndDispose()
|
spelling = clang_getTypeSpelling(type).convertAndDispose().dropConstQualifier()
|
||||||
)
|
)
|
||||||
|
|
||||||
CXTypeKind.CXType_SChar, CXTypeKind.CXType_Short,
|
CXTypeKind.CXType_SChar, CXTypeKind.CXType_Short,
|
||||||
CXTypeKind.CXType_Int, CXTypeKind.CXType_Long, CXTypeKind.CXType_LongLong -> IntegerType(
|
CXTypeKind.CXType_Int, CXTypeKind.CXType_Long, CXTypeKind.CXType_LongLong -> IntegerType(
|
||||||
size = type.getSize().toInt(),
|
size = type.getSize().toInt(),
|
||||||
isSigned = true,
|
isSigned = true,
|
||||||
spelling = clang_getTypeSpelling(type).convertAndDispose()
|
spelling = clang_getTypeSpelling(type).convertAndDispose().dropConstQualifier()
|
||||||
)
|
)
|
||||||
|
|
||||||
CXTypeKind.CXType_Float, CXTypeKind.CXType_Double -> FloatingType(
|
CXTypeKind.CXType_Float, CXTypeKind.CXType_Double -> FloatingType(
|
||||||
size = type.getSize().toInt(),
|
size = type.getSize().toInt(),
|
||||||
spelling = clang_getTypeSpelling(type).convertAndDispose()
|
spelling = clang_getTypeSpelling(type).convertAndDispose().dropConstQualifier()
|
||||||
)
|
)
|
||||||
|
|
||||||
CXTypeKind.CXType_Bool -> CBoolType
|
CXTypeKind.CXType_Bool -> CBoolType
|
||||||
|
|||||||
+2
-1
@@ -264,7 +264,8 @@ open class BoolType: PrimitiveType
|
|||||||
object CBoolType : BoolType()
|
object CBoolType : BoolType()
|
||||||
|
|
||||||
object ObjCBoolType : 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
|
data class IntegerType(val size: Int, val isSigned: Boolean, val spelling: String) : PrimitiveType
|
||||||
|
|
||||||
// TODO: floating type is not actually defined entirely by its size.
|
// TODO: floating type is not actually defined entirely by its size.
|
||||||
|
|||||||
+48
-2
@@ -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.gen.jvm.KotlinPlatform
|
||||||
import org.jetbrains.kotlin.native.interop.indexer.ObjCProtocol
|
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
|
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||||
|
|
||||||
class BridgeBuilderResult(
|
class BridgeBuilderResult(
|
||||||
@@ -16,6 +18,8 @@ class BridgeBuilderResult(
|
|||||||
val excludedStubs: Set<StubIrElement>
|
val excludedStubs: Set<StubIrElement>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private data class CCalleeWrapper(val name: String, val lines: List<String>)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates [NativeBridges] and corresponding function bodies and property accessors.
|
* Generates [NativeBridges] and corresponding function bodies and property accessors.
|
||||||
*/
|
*/
|
||||||
@@ -68,6 +72,12 @@ class StubIrBridgeBuilder(
|
|||||||
private val excludedStubs = mutableSetOf<StubIrElement>()
|
private val excludedStubs = mutableSetOf<StubIrElement>()
|
||||||
|
|
||||||
private val bridgeGeneratingVisitor = object : StubIrVisitor<StubContainer?, Unit> {
|
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?) {
|
override fun visitClass(element: ClassStub, owner: StubContainer?) {
|
||||||
element.annotations.filterIsInstance<AnnotationStub.ObjC.ExternalClass>().firstOrNull()?.let {
|
element.annotations.filterIsInstance<AnnotationStub.ObjC.ExternalClass>().firstOrNull()?.let {
|
||||||
if (it.protocolGetter.isNotEmpty() && element.origin is StubOrigin.ObjCProtocol) {
|
if (it.protocolGetter.isNotEmpty() && element.origin is StubOrigin.ObjCProtocol) {
|
||||||
@@ -104,14 +114,49 @@ class StubIrBridgeBuilder(
|
|||||||
val cCallAnnotation = function.annotations.firstIsInstanceOrNull<AnnotationStub.CCall.Symbol>()
|
val cCallAnnotation = function.annotations.firstIsInstanceOrNull<AnnotationStub.CCall.Symbol>()
|
||||||
?: return
|
?: return
|
||||||
val cCallSymbolName = cCallAnnotation.symbolName
|
val cCallSymbolName = cCallAnnotation.symbolName
|
||||||
|
val (wrapperName, wrapperLines) = generateCCalleeWrapper(origin)
|
||||||
simpleBridgeGenerator.insertNativeBridge(
|
simpleBridgeGenerator.insertNativeBridge(
|
||||||
function,
|
function,
|
||||||
emptyList(),
|
emptyList(),
|
||||||
listOf("extern const void* $cCallSymbolName __asm(${cCallSymbolName.quoteAsKotlinLiteral()});",
|
listOf(
|
||||||
"extern const void* $cCallSymbolName = &${origin.function.name};")
|
*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?) {
|
override fun visitProperty(element: PropertyStub, owner: StubContainer?) {
|
||||||
try {
|
try {
|
||||||
when (val kind = element.kind) {
|
when (val kind = element.kind) {
|
||||||
@@ -226,6 +271,7 @@ class StubIrBridgeBuilder(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun generateBridgeBody(function: FunctionStub) {
|
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}" }
|
assert(function.origin is StubOrigin.Function) { "Can't create bridge for ${function.name}" }
|
||||||
val origin = function.origin as StubOrigin.Function
|
val origin = function.origin as StubOrigin.Function
|
||||||
val bodyGenerator = KotlinCodeBuilder(scope = kotlinFile)
|
val bodyGenerator = KotlinCodeBuilder(scope = kotlinFile)
|
||||||
|
|||||||
+6
-4
@@ -349,13 +349,15 @@ internal class FunctionStubBuilder(
|
|||||||
|
|
||||||
val annotations: List<AnnotationStub>
|
val annotations: List<AnnotationStub>
|
||||||
val mustBeExternal: Boolean
|
val mustBeExternal: Boolean
|
||||||
if (!func.isVararg || platform != KotlinPlatform.NATIVE) {
|
if (platform == KotlinPlatform.JVM) {
|
||||||
annotations = emptyList()
|
annotations = emptyList()
|
||||||
mustBeExternal = false
|
mustBeExternal = false
|
||||||
} else {
|
} else {
|
||||||
val type = WrapperStubType(KotlinTypes.any.makeNullable())
|
if (func.isVararg) {
|
||||||
parameters += FunctionParameterStub("variadicArguments", type, isVararg = true)
|
val type = WrapperStubType(KotlinTypes.any.makeNullable())
|
||||||
annotations = listOf(AnnotationStub.CCall.Symbol(context.generateNextUniqueId("knifunptr_")))
|
parameters += FunctionParameterStub("variadicArguments", type, isVararg = true)
|
||||||
|
}
|
||||||
|
annotations = listOf(AnnotationStub.CCall.Symbol(context.generateNextUniqueId("knifunptr_") + "_${func.name}"))
|
||||||
mustBeExternal = true
|
mustBeExternal = true
|
||||||
}
|
}
|
||||||
val functionStub = FunctionStub(
|
val functionStub = FunctionStub(
|
||||||
|
|||||||
+16
-15
@@ -217,30 +217,31 @@ class StubIrTextEmitter(
|
|||||||
override fun visitFunction(element: FunctionStub, owner: StubContainer?) {
|
override fun visitFunction(element: FunctionStub, owner: StubContainer?) {
|
||||||
if (element in bridgeBuilderResult.excludedStubs) return
|
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 {
|
element.annotations.forEach {
|
||||||
out(renderAnnotation(it))
|
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 {
|
when {
|
||||||
element.external -> out("external $header")
|
element.external -> out("external $header")
|
||||||
element.isOptionalObjCMethod() -> out("$header = optional()")
|
element.isOptionalObjCMethod() -> out("$header = optional()")
|
||||||
owner != null && owner.isInterface -> out(header)
|
owner != null && owner.isInterface -> out(header)
|
||||||
else -> if (nativeBridges.isSupported(element)) {
|
else -> block(header) {
|
||||||
block(header) {
|
functionBridgeBodies.getValue(element).forEach(out)
|
||||||
functionBridgeBodies.getValue(element).forEach(out)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sequenceOf(
|
|
||||||
annotationForUnableToImport,
|
|
||||||
"$header = throw UnsupportedOperationException()"
|
|
||||||
).forEach(out)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitProperty(element: PropertyStub, owner: StubContainer?) {
|
override fun visitProperty(element: PropertyStub, owner: StubContainer?) {
|
||||||
|
|||||||
+3
-2
@@ -38,7 +38,8 @@ fun Type.getStringRepresentation(): String = when (this) {
|
|||||||
is IntegerType -> this.spelling
|
is IntegerType -> this.spelling
|
||||||
is FloatingType -> 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
|
is RecordType -> this.decl.spelling
|
||||||
|
|
||||||
@@ -58,7 +59,7 @@ fun Type.getStringRepresentation(): String = when (this) {
|
|||||||
is ObjCBlockPointer -> "id"
|
is ObjCBlockPointer -> "id"
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> throw kotlin.NotImplementedError()
|
else -> throw NotImplementedError()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPointerTypeStringRepresentation(pointee: Type): String =
|
fun getPointerTypeStringRepresentation(pointee: Type): String =
|
||||||
|
|||||||
Reference in New Issue
Block a user