From e3f4d09aebe5730230c79812d6271fe8688dd073 Mon Sep 17 00:00:00 2001 From: Sergey Bogolepov <1580082+sbogolepov@users.noreply.github.com> Date: Thu, 29 Aug 2019 11:04:10 +0700 Subject: [PATCH] Generate C wrapper instead of bridge for all functions. (#3153) [C Interop] Generate C wrapper instead of bridge for all functions. --- .../kotlin/native/interop/indexer/Indexer.kt | 11 ++-- .../native/interop/indexer/NativeIndex.kt | 3 +- .../native/interop/gen/StubIrBridgeBuilder.kt | 50 ++++++++++++++++++- .../interop/gen/StubIrElementBuilders.kt | 10 ++-- .../native/interop/gen/StubIrTextEmitter.kt | 31 ++++++------ .../kotlin/native/interop/gen/TypeUtils.kt | 5 +- 6 files changed, 83 insertions(+), 27 deletions(-) diff --git a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt index 09b1b0ba4d4..08bb7e6463a 100644 --- a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt +++ b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt @@ -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): 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 diff --git a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt index b209520f9fd..b102e1b64d0 100644 --- a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt +++ b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt @@ -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. diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrBridgeBuilder.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrBridgeBuilder.kt index 6eb7624da22..aa350d6de98 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrBridgeBuilder.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrBridgeBuilder.kt @@ -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 ) +private data class CCalleeWrapper(val name: String, val lines: List) + /** * Generates [NativeBridges] and corresponding function bodies and property accessors. */ @@ -68,6 +72,12 @@ class StubIrBridgeBuilder( private val excludedStubs = mutableSetOf() private val bridgeGeneratingVisitor = object : StubIrVisitor { + + private var currentFunctionWrapperId = 0 + + private fun generateFunctionWrapperName(functionName: String) = + "${functionName}_wrapper${currentFunctionWrapperId++}" + override fun visitClass(element: ClassStub, owner: StubContainer?) { element.annotations.filterIsInstance().firstOrNull()?.let { if (it.protocolGetter.isNotEmpty() && element.origin is StubOrigin.ObjCProtocol) { @@ -104,14 +114,49 @@ class StubIrBridgeBuilder( val cCallAnnotation = function.annotations.firstIsInstanceOrNull() ?: 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) diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrElementBuilders.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrElementBuilders.kt index 48a182c922f..2230698a8b0 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrElementBuilders.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrElementBuilders.kt @@ -349,13 +349,15 @@ internal class FunctionStubBuilder( val annotations: List 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( diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrTextEmitter.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrTextEmitter.kt index fed7ff7b7b5..5e4eec8edad 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrTextEmitter.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrTextEmitter.kt @@ -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?) { diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/TypeUtils.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/TypeUtils.kt index 2c8cf478d94..c35b0828fd6 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/TypeUtils.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/TypeUtils.kt @@ -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 =