[C Interop] Use CCall annotation for global getters and setters. (#3420)

This commit is contained in:
Sergey Bogolepov
2019-10-07 10:00:17 +03:00
committed by GitHub
parent 25820bd227
commit c42a53bcc9
6 changed files with 209 additions and 102 deletions
@@ -4,7 +4,11 @@ package kotlinx.cinterop.internal
@Retention(AnnotationRetention.BINARY)
annotation class CStruct(val spelling: String)
@Target(AnnotationTarget.FUNCTION)
@Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
@Retention(AnnotationRetention.BINARY)
public annotation class CCall(val id: String) {
@Target(AnnotationTarget.VALUE_PARAMETER)
@@ -0,0 +1,82 @@
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.indexer.FunctionDecl
import org.jetbrains.kotlin.native.interop.indexer.GlobalDecl
import org.jetbrains.kotlin.native.interop.indexer.VoidType
import org.jetbrains.kotlin.native.interop.indexer.unwrapTypedefs
internal data class CCalleeWrapper(val lines: List<String>)
/**
* Some functions don't have an address (e.g. macros-based or builtins).
* To solve this problem we generate a wrapper function.
*/
internal class CWrappersGenerator(private val context: StubIrContext) {
private var currentFunctionWrapperId = 0
private val packageName =
context.configuration.pkgName.replace(INVALID_CLANG_IDENTIFIER_REGEX, "_")
private fun generateFunctionWrapperName(functionName: String): String {
return "${packageName}_${functionName}_wrapper${currentFunctionWrapperId++}"
}
private fun bindSymbolToFunction(symbol: String, function: String): List<String> = listOf(
"const void* $symbol __asm(${symbol.quoteAsKotlinLiteral()});",
"const void* $symbol = &$function;"
)
private data class Parameter(val type: String, val name: String)
private fun createWrapper(
symbolName: String,
wrapperName: String,
returnType: String,
parameters: List<Parameter>,
body: String
): List<String> = listOf(
"__attribute__((always_inline))",
"$returnType $wrapperName(${parameters.joinToString { "${it.type} ${it.name}" }}) {",
body,
"}",
*bindSymbolToFunction(symbolName, wrapperName).toTypedArray()
)
fun generateCCalleeWrapper(function: FunctionDecl, symbolName: String): CCalleeWrapper =
if (function.isVararg) {
CCalleeWrapper(bindSymbolToFunction(symbolName, function.name))
} else {
val wrapperName = generateFunctionWrapperName(function.name)
val returnType = function.returnType.getStringRepresentation()
val parameters = function.parameters.mapIndexed { index, parameter ->
Parameter(parameter.type.getStringRepresentation(), "p$index")
}
val callExpression = "${function.name}(${parameters.joinToString { it.name }});"
val wrapperBody = if (function.returnType.unwrapTypedefs() is VoidType) {
callExpression
} else {
"return $callExpression"
}
val wrapper = createWrapper(symbolName, wrapperName, returnType, parameters, wrapperBody)
CCalleeWrapper(wrapper)
}
fun generateCGlobalGetter(globalDecl: GlobalDecl, symbolName: String): CCalleeWrapper {
val wrapperName = generateFunctionWrapperName("${globalDecl.name}_getter")
val returnType = globalDecl.type.getStringRepresentation()
val wrapperBody = "return ${globalDecl.name};"
val wrapper = createWrapper(symbolName, wrapperName, returnType, emptyList(), wrapperBody)
return CCalleeWrapper(wrapper)
}
fun generateCGlobalSetter(globalDecl: GlobalDecl, symbolName: String): CCalleeWrapper {
val wrapperName = generateFunctionWrapperName("${globalDecl.name}_setter")
val globalType = globalDecl.type.getStringRepresentation()
val parameter = Parameter(globalType, "p1")
val wrapperBody = "${globalDecl.name} = ${parameter.name};"
val wrapper = createWrapper(symbolName, wrapperName, "void", listOf(parameter), wrapperBody)
return CCalleeWrapper(wrapper)
}
}
@@ -21,6 +21,8 @@ import org.jetbrains.kotlin.native.interop.indexer.CompilationWithPCH
import org.jetbrains.kotlin.native.interop.indexer.Language
import org.jetbrains.kotlin.native.interop.indexer.mapFragmentIsCompilable
internal val INVALID_CLANG_IDENTIFIER_REGEX = "[^a-zA-Z1-9_]".toRegex()
class SimpleBridgeGeneratorImpl(
private val platform: KotlinPlatform,
private val pkgName: String,
@@ -246,8 +248,4 @@ class SimpleBridgeGeneratorImpl(
nativeBacked !in excludedClients
}
}
companion object {
internal val INVALID_CLANG_IDENTIFIER_REGEX = "[^a-zA-Z1-9_]".toRegex()
}
}
@@ -4,11 +4,8 @@
*/
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.gen.SimpleBridgeGeneratorImpl.Companion.INVALID_CLANG_IDENTIFIER_REGEX
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.native.interop.indexer.*
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
class BridgeBuilderResult(
@@ -19,8 +16,6 @@ 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.
*/
@@ -30,6 +25,8 @@ class StubIrBridgeBuilder(
private val globalAddressExpressions = mutableMapOf<Pair<String, PropertyAccessor>, KotlinExpression>()
private val wrapperGenerator = CWrappersGenerator(context)
private fun getGlobalAddressExpression(cGlobalName: String, accessor: PropertyAccessor) =
globalAddressExpressions.getOrPut(Pair(cGlobalName, accessor)) {
simpleBridgeGenerator.kotlinToNative(
@@ -74,13 +71,6 @@ class StubIrBridgeBuilder(
private val bridgeGeneratingVisitor = object : StubIrVisitor<StubContainer?, Unit> {
private var currentFunctionWrapperId = 0
private fun generateFunctionWrapperName(packageName: String, functionName: String): String {
val validPackageName = packageName.replace(INVALID_CLANG_IDENTIFIER_REGEX, "_")
return "${validPackageName}_${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) {
@@ -116,50 +106,10 @@ class StubIrBridgeBuilder(
?: return
val cCallAnnotation = function.annotations.firstIsInstanceOrNull<AnnotationStub.CCall.Symbol>()
?: return
val cCallSymbolName = cCallAnnotation.symbolName
val (wrapperName, wrapperLines) = generateCCalleeWrapper(origin)
simpleBridgeGenerator.insertNativeBridge(
function,
emptyList(),
listOf(
*wrapperLines.toTypedArray(),
"const void* $cCallSymbolName __asm(${cCallSymbolName.quoteAsKotlinLiteral()});",
"const void* $cCallSymbolName = &$wrapperName;"
)
)
val wrapper = wrapperGenerator.generateCCalleeWrapper(origin.function, cCallAnnotation.symbolName)
simpleBridgeGenerator.insertNativeBridge(function, emptyList(), wrapper.lines)
}
/**
* 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(context.configuration.pkgName, 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) {
@@ -185,14 +135,11 @@ class StubIrBridgeBuilder(
override fun visitPropertyAccessor(accessor: PropertyAccessor, owner: StubContainer?) {
when (accessor) {
is PropertyAccessor.Getter.SimpleGetter -> {
if (accessor in builderResult.bridgeGenerationComponents.getterToBridgeInfo) {
val extra = builderResult.bridgeGenerationComponents.getterToBridgeInfo.getValue(accessor)
val typeInfo = extra.typeInfo
val expression = if (extra.isArray) {
val getAddressExpression = getGlobalAddressExpression(extra.cGlobalName, accessor)
typeInfo.argFromBridged(getAddressExpression, kotlinFile, nativeBacked = accessor) + "!!"
} else {
typeInfo.argFromBridged(simpleBridgeGenerator.kotlinToNative(
when (accessor) {
in builderResult.bridgeGenerationComponents.getterToBridgeInfo -> {
val extra = builderResult.bridgeGenerationComponents.getterToBridgeInfo.getValue(accessor)
val typeInfo = extra.typeInfo
propertyAccessorBridgeBodies[accessor] = typeInfo.argFromBridged(simpleBridgeGenerator.kotlinToNative(
nativeBacked = accessor,
returnType = typeInfo.bridgedType,
kotlinValues = emptyList(),
@@ -201,7 +148,12 @@ class StubIrBridgeBuilder(
typeInfo.cToBridged(expr = extra.cGlobalName)
}, kotlinFile, nativeBacked = accessor)
}
propertyAccessorBridgeBodies[accessor] = expression
in builderResult.bridgeGenerationComponents.arrayGetterInfo -> {
val extra = builderResult.bridgeGenerationComponents.arrayGetterInfo.getValue(accessor)
val typeInfo = extra.typeInfo
val getAddressExpression = getGlobalAddressExpression(extra.cGlobalName, accessor)
propertyAccessorBridgeBodies[accessor] = typeInfo.argFromBridged(getAddressExpression, kotlinFile, nativeBacked = accessor) + "!!"
}
}
}
@@ -245,6 +197,26 @@ class StubIrBridgeBuilder(
val getAddressExpression = getGlobalAddressExpression(accessor.cGlobalName, accessor)
propertyAccessorBridgeBodies[accessor] = getAddressExpression
}
is PropertyAccessor.Getter.ExternalGetter -> {
if (accessor in builderResult.wrapperGenerationComponents.getterToWrapperInfo) {
val extra = builderResult.wrapperGenerationComponents.getterToWrapperInfo.getValue(accessor)
val cCallAnnotation = accessor.annotations.firstIsInstanceOrNull<AnnotationStub.CCall.Symbol>()
?: error("external getter for ${extra.global.name} wasn't marked with @CCall")
val wrapper = wrapperGenerator.generateCGlobalGetter(extra.global, cCallAnnotation.symbolName)
simpleBridgeGenerator.insertNativeBridge(accessor, emptyList(), wrapper.lines)
}
}
is PropertyAccessor.Setter.ExternalSetter -> {
if (accessor in builderResult.wrapperGenerationComponents.setterToWrapperInfo) {
val extra = builderResult.wrapperGenerationComponents.setterToWrapperInfo.getValue(accessor)
val cCallAnnotation = accessor.annotations.firstIsInstanceOrNull<AnnotationStub.CCall.Symbol>()
?: error("external setter for ${extra.global.name} wasn't marked with @CCall")
val wrapper = wrapperGenerator.generateCGlobalSetter(extra.global, cCallAnnotation.symbolName)
simpleBridgeGenerator.insertNativeBridge(accessor, emptyList(), wrapper.lines)
}
}
}
}
@@ -8,25 +8,22 @@ import org.jetbrains.kotlin.native.interop.gen.jvm.InteropConfiguration
import org.jetbrains.kotlin.native.interop.gen.jvm.KotlinPlatform
import org.jetbrains.kotlin.native.interop.indexer.*
/**
* Components that are not passed via StubIr but required for bridge generation.
*/
class BridgeGenerationInfo(val cGlobalName: String, val typeInfo: TypeInfo)
/**
* Additional components that are required to generate bridges.
* TODO: Metadata-based interop should not depend on these components.
*/
interface BridgeGenerationComponents {
class GlobalSetterBridgeInfo(
val cGlobalName: String,
val typeInfo: TypeInfo
)
val setterToBridgeInfo: Map<PropertyAccessor.Setter, BridgeGenerationInfo>
class GlobalGetterBridgeInfo(
val cGlobalName: String,
val typeInfo: TypeInfo,
val isArray: Boolean
)
val getterToBridgeInfo: Map<PropertyAccessor.Getter, BridgeGenerationInfo>
val setterToBridgeInfo: Map<PropertyAccessor.Setter, GlobalSetterBridgeInfo>
val getterToBridgeInfo: Map<PropertyAccessor.Getter, GlobalGetterBridgeInfo>
val arrayGetterInfo: Map<PropertyAccessor.Getter, BridgeGenerationInfo>
val enumToTypeMirror: Map<ClassStub.Enum, TypeMirror>
@@ -35,13 +32,15 @@ interface BridgeGenerationComponents {
val cStringParameters: Set<FunctionParameterStub>
}
class BridgeGenerationComponentsBuilder(
val getterToBridgeInfo: MutableMap<PropertyAccessor.Getter, BridgeGenerationComponents.GlobalGetterBridgeInfo> = mutableMapOf(),
val setterToBridgeInfo: MutableMap<PropertyAccessor.Setter, BridgeGenerationComponents.GlobalSetterBridgeInfo> = mutableMapOf(),
val enumToTypeMirror: MutableMap<ClassStub.Enum, TypeMirror> = mutableMapOf(),
val wCStringParameters: MutableSet<FunctionParameterStub> = mutableSetOf(),
val cStringParameters: MutableSet<FunctionParameterStub> = mutableSetOf()
) {
class BridgeGenerationComponentsBuilder {
val getterToBridgeInfo = mutableMapOf<PropertyAccessor.Getter, BridgeGenerationInfo>()
val setterToBridgeInfo = mutableMapOf<PropertyAccessor.Setter, BridgeGenerationInfo>()
val arrayGetterBridgeInfo = mutableMapOf<PropertyAccessor.Getter, BridgeGenerationInfo>()
val enumToTypeMirror = mutableMapOf<ClassStub.Enum, TypeMirror>()
val wCStringParameters = mutableSetOf<FunctionParameterStub>()
val cStringParameters = mutableSetOf<FunctionParameterStub>()
fun build(): BridgeGenerationComponents = object : BridgeGenerationComponents {
override val getterToBridgeInfo =
this@BridgeGenerationComponentsBuilder.getterToBridgeInfo.toMap()
@@ -57,6 +56,31 @@ class BridgeGenerationComponentsBuilder(
override val cStringParameters: Set<FunctionParameterStub> =
this@BridgeGenerationComponentsBuilder.cStringParameters.toSet()
override val arrayGetterInfo: Map<PropertyAccessor.Getter, BridgeGenerationInfo> =
this@BridgeGenerationComponentsBuilder.arrayGetterBridgeInfo.toMap()
}
}
/**
* Components that are not passed via StubIr but required for generation of wrappers.
*/
class WrapperGenerationInfo(val global: GlobalDecl)
interface WrapperGenerationComponents {
val getterToWrapperInfo: Map<PropertyAccessor.Getter.ExternalGetter, WrapperGenerationInfo>
val setterToWrapperInfo: Map<PropertyAccessor.Setter.ExternalSetter, WrapperGenerationInfo>
}
class WrapperGenerationComponentsBuilder {
val getterToWrapperInfo = mutableMapOf<PropertyAccessor.Getter.ExternalGetter, WrapperGenerationInfo>()
val setterToWrapperInfo = mutableMapOf<PropertyAccessor.Setter.ExternalSetter, WrapperGenerationInfo>()
fun build(): WrapperGenerationComponents = object : WrapperGenerationComponents {
override val getterToWrapperInfo = this@WrapperGenerationComponentsBuilder.getterToWrapperInfo.toMap()
override val setterToWrapperInfo = this@WrapperGenerationComponentsBuilder.setterToWrapperInfo.toMap()
}
}
@@ -86,6 +110,8 @@ interface StubsBuildingContext {
val bridgeComponentsBuilder: BridgeGenerationComponentsBuilder
val wrapperComponentsBuilder: WrapperGenerationComponentsBuilder
fun getKotlinClassFor(objCClassOrProtocol: ObjCClassOrProtocol, isMeta: Boolean = false): Classifier
fun getKotlinClassForPointed(structDecl: StructDecl): Classifier
@@ -210,6 +236,8 @@ class StubsBuildingContextImpl(
override val bridgeComponentsBuilder = BridgeGenerationComponentsBuilder()
override val wrapperComponentsBuilder = WrapperGenerationComponentsBuilder()
override fun getKotlinClassFor(objCClassOrProtocol: ObjCClassOrProtocol, isMeta: Boolean): Classifier {
return declarationMapper.getKotlinClassFor(objCClassOrProtocol, isMeta)
}
@@ -223,7 +251,8 @@ class StubsBuildingContextImpl(
data class StubIrBuilderResult(
val stubs: SimpleStubContainer,
val declarationMapper: DeclarationMapper,
val bridgeGenerationComponents: BridgeGenerationComponents
val bridgeGenerationComponents: BridgeGenerationComponents,
val wrapperGenerationComponents: WrapperGenerationComponents
)
/**
@@ -285,7 +314,8 @@ class StubIrBuilder(private val context: StubIrContext) {
return StubIrBuilderResult(
stubs,
buildingContext.declarationMapper,
buildingContext.bridgeComponentsBuilder.build()
buildingContext.bridgeComponentsBuilder.build(),
buildingContext.wrapperComponentsBuilder.build()
)
}
@@ -110,9 +110,8 @@ internal class StructStubBuilder(
val signed = field.type.isIntegerTypeSigned()
val readBits = PropertyAccessor.Getter.ReadBits(field.offset, field.size, signed)
val writeBits = PropertyAccessor.Setter.WriteBits(field.offset, field.size)
// TODO: Use something instead of [GlobalGetterBridgeInfo].
context.bridgeComponentsBuilder.getterToBridgeInfo[readBits] = BridgeGenerationComponents.GlobalGetterBridgeInfo("", typeInfo, false)
context.bridgeComponentsBuilder.setterToBridgeInfo[writeBits] = BridgeGenerationComponents.GlobalSetterBridgeInfo("", typeInfo)
context.bridgeComponentsBuilder.getterToBridgeInfo[readBits] = BridgeGenerationInfo("", typeInfo)
context.bridgeComponentsBuilder.setterToBridgeInfo[writeBits] = BridgeGenerationInfo("", typeInfo)
val kind = PropertyStub.Kind.Var(readBits, writeBits)
PropertyStub(field.name, WrapperStubType(kotlinType), kind)
}
@@ -357,7 +356,7 @@ internal class FunctionStubBuilder(
val type = WrapperStubType(KotlinTypes.any.makeNullable())
parameters += FunctionParameterStub("variadicArguments", type, isVararg = true)
}
annotations = listOf(AnnotationStub.CCall.Symbol(context.generateNextUniqueId("knifunptr_") + "_${func.name}"))
annotations = listOf(AnnotationStub.CCall.Symbol("${context.generateNextUniqueId("knifunptr_")}_${func.name}"))
mustBeExternal = true
}
val functionStub = FunctionStub(
@@ -443,22 +442,44 @@ internal class GlobalStubBuilder(
if (unwrappedType is ArrayType) {
kotlinType = (mirror as TypeMirror.ByValue).valueType
val getter = PropertyAccessor.Getter.SimpleGetter()
val extra = BridgeGenerationComponents.GlobalGetterBridgeInfo(global.name, mirror.info, isArray = true)
context.bridgeComponentsBuilder.getterToBridgeInfo[getter] = extra
val extra = BridgeGenerationInfo(global.name, mirror.info)
context.bridgeComponentsBuilder.arrayGetterBridgeInfo[getter] = extra
kind = PropertyStub.Kind.Val(getter)
} else {
when (mirror) {
is TypeMirror.ByValue -> {
kotlinType = mirror.argType
val getter = PropertyAccessor.Getter.SimpleGetter()
val getterExtra = BridgeGenerationComponents.GlobalGetterBridgeInfo(global.name, mirror.info, isArray = false)
context.bridgeComponentsBuilder.getterToBridgeInfo[getter] = getterExtra
val getter = when (context.platform) {
KotlinPlatform.JVM -> {
PropertyAccessor.Getter.SimpleGetter().also {
val getterExtra = BridgeGenerationInfo(global.name, mirror.info)
context.bridgeComponentsBuilder.getterToBridgeInfo[it] = getterExtra
}
}
KotlinPlatform.NATIVE -> {
val cCallAnnotation = AnnotationStub.CCall.Symbol("${context.generateNextUniqueId("knifunptr_")}_${global.name}_getter")
PropertyAccessor.Getter.ExternalGetter(listOf(cCallAnnotation)).also {
context.wrapperComponentsBuilder.getterToWrapperInfo[it] = WrapperGenerationInfo(global)
}
}
}
kind = if (global.isConst) {
PropertyStub.Kind.Val(getter)
} else {
val setter = PropertyAccessor.Setter.SimpleSetter()
val setterExtra = BridgeGenerationComponents.GlobalSetterBridgeInfo(global.name, mirror.info)
context.bridgeComponentsBuilder.setterToBridgeInfo[setter] = setterExtra
val setter = when (context.platform) {
KotlinPlatform.JVM -> {
PropertyAccessor.Setter.SimpleSetter().also {
val setterExtra = BridgeGenerationInfo(global.name, mirror.info)
context.bridgeComponentsBuilder.setterToBridgeInfo[it] = setterExtra
}
}
KotlinPlatform.NATIVE -> {
val cCallAnnotation = AnnotationStub.CCall.Symbol("${context.generateNextUniqueId("knifunptr_")}_${global.name}_setter")
PropertyAccessor.Setter.ExternalSetter(listOf(cCallAnnotation)).also {
context.wrapperComponentsBuilder.setterToWrapperInfo[it] = WrapperGenerationInfo(global)
}
}
}
PropertyStub.Kind.Var(getter, setter)
}
}