[Kotlin/Native][Interop] Skia interop plugin for cinterop

This commit is contained in:
Alexander Gorshenev
2020-12-21 03:11:45 +03:00
parent 5f582ad28a
commit 887032667d
62 changed files with 1160 additions and 1155 deletions
@@ -23,8 +23,6 @@ apply plugin: 'application'
mainClassName = "org.jetbrains.kotlin.native.interop.gen.jvm.MainKt"
dependencies {
implementation project(":kotlin-native:Interop:Indexer")
implementation project(":kotlin-native:utilities:basic-utils")
@@ -56,4 +54,4 @@ sourceSets{
srcDir(VersionGeneratorKt.kotlinNativeVersionSrc(project))
}
}
}
}
@@ -4,13 +4,15 @@
*/
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
import org.jetbrains.kotlin.native.interop.indexer.*
internal data class CCalleeWrapper(val lines: List<String>)
open class ManagedTypePassing {
open val ManagedType.passValue: String get() = error("ManagedType support requires a plugin")
open val ManagedType.returnValue: String get() = error("ManagedType support requires a plugin")
}
/**
* Some functions don't have an address (e.g. macros-based or builtins).
* To solve this problem we generate a wrapper function.
@@ -26,10 +28,17 @@ internal class CWrappersGenerator(private val context: StubIrContext) {
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 fun bindSymbolToFunction(symbol: String, function: String): List<String> {
val prefix = if (context.configuration.library.language == Language.CPP)
"extern \"C\" "
else
""
return listOf(
"${prefix}const void* $symbol __asm(${symbol.quoteAsKotlinLiteral()});",
"${prefix}const void* $symbol = (const void*)&$function;"
)
}
private data class Parameter(val type: String, val name: String)
@@ -42,34 +51,122 @@ internal class CWrappersGenerator(private val context: StubIrContext) {
): List<String> = listOf(
"__attribute__((always_inline))",
"$returnType $wrapperName(${parameters.joinToString { "${it.type} ${it.name}" }}) {",
body,
"\t$body",
"}",
*bindSymbolToFunction(symbolName, wrapperName).toTypedArray()
)
private val Type.stringRepresentation get() = this.getStringRepresentation(context.plugin)
private fun createCCalleeWrapper(function: FunctionDecl, symbolName: String): List<String> {
assert(context.configuration.library.language != Language.CPP)
val wrapperName = generateFunctionWrapperName(function.name)
val returnType = function.returnType.stringRepresentation
val parameters = function.parameters.mapIndexed { index, parameter ->
val type = parameter.type.stringRepresentation
Parameter(type, "p$index")
}
val callExpression = "${function.name}(${parameters.joinToString { it.name }})"
val wrapperBody = if (function.returnType.unwrapTypedefs() is VoidType) {
"$callExpression;"
} else {
"return (${returnType})($callExpression);"
}
return createWrapper(symbolName, wrapperName, returnType, parameters, wrapperBody)
}
private fun createCppCalleeWrapper(function: FunctionDecl, symbolName: String): List<String> {
assert(context.configuration.library.language == Language.CPP)
val wrapperName = generateFunctionWrapperName(function.name)
val returnType = function.returnType.stringRepresentation
val unwrappedReturnType = function.returnType.unwrapTypedefs()
val returnTypePrefix =
if (unwrappedReturnType is PointerType && unwrappedReturnType.isLVReference) "&" else ""
val returnTypePostfix =
if (unwrappedReturnType is ManagedType)
with(context.plugin.managedTypePassing) { unwrappedReturnType.returnValue }
else ""
val parameters = function.parameters.mapIndexed { index, parameter ->
val type = parameter.type.stringRepresentation
Parameter(type, "p$index")
}
val argumentTypes = function.parameters.map { parameter ->
val parameterTypeText = parameter.type.stringRepresentation
val type = parameter.type
val unwrappedType = type.unwrapTypedefs()
val cppRefTypePrefix =
if (unwrappedType is PointerType && unwrappedType.isLVReference) "*" else ""
val typeExpression = when {
type is Typedef ->
"(${type.def.name})"
type is PointerType && type.spelling != null ->
"(${type.spelling})$cppRefTypePrefix"
unwrappedType is EnumType ->
"(${unwrappedType.def.spelling})"
unwrappedType is RecordType ->
"*(${unwrappedType.decl.spelling}*)"
unwrappedType is ManagedType -> {
with(context.plugin.managedTypePassing) { unwrappedType.passValue }
}
else ->
"$cppRefTypePrefix($parameterTypeText)"
}
typeExpression
}
val callExpression = with (function) {
assert(argumentTypes.size == parameters.size)
val arguments = argumentTypes.mapIndexed { index, type ->
"${type}(${parameters[index].name})"
}
when {
isCxxInstanceMethod -> {
val parametersPart = arguments.drop(1).joinToString()
"(${parameters[0].name})->${name}($parametersPart)"
}
isCxxConstructor -> {
val parametersPart = arguments.drop(1).joinToString()
"new(${parameters[0].name}) ${cxxReceiverClass!!.spelling}($parametersPart)"
}
isCxxDestructor ->
"(${parameters[0].name})->~${cxxReceiverClass!!.spelling.substringAfterLast(':')}()"
else -> "${fullName}(${arguments.joinToString()})"
}
}
val wrapperBody = if (function.returnType.unwrapTypedefs() is VoidType) {
"$callExpression;"
} else {
"return (${returnType})$returnTypePrefix($callExpression)$returnTypePostfix;"
}
return createWrapper(symbolName, wrapperName, returnType, parameters, wrapperBody)
}
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
val wrapper = if (context.configuration.library.language == Language.CPP) {
createCppCalleeWrapper(function, symbolName)
} else {
"return $callExpression"
createCCalleeWrapper(function, symbolName)
}
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 returnType = globalDecl.type.stringRepresentation
val wrapperBody = "return ${globalDecl.name};"
val wrapper = createWrapper(symbolName, wrapperName, returnType, emptyList(), wrapperBody)
return CCalleeWrapper(wrapper)
@@ -85,7 +182,7 @@ internal class CWrappersGenerator(private val context: StubIrContext) {
fun generateCGlobalSetter(globalDecl: GlobalDecl, symbolName: String): CCalleeWrapper {
val wrapperName = generateFunctionWrapperName("${globalDecl.name}_setter")
val globalType = globalDecl.type.getStringRepresentation()
val globalType = globalDecl.type.stringRepresentation
val parameter = Parameter(globalType, "p1")
val wrapperBody = "${globalDecl.name} = ${parameter.name};"
val wrapper = createWrapper(symbolName, wrapperName, "void", listOf(parameter), wrapperBody)
@@ -16,7 +16,10 @@
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.indexer.*
import org.jetbrains.kotlin.native.interop.indexer.RecordType
import org.jetbrains.kotlin.native.interop.indexer.Type
import org.jetbrains.kotlin.native.interop.indexer.VoidType
import org.jetbrains.kotlin.native.interop.indexer.unwrapTypedefs
/**
* The [MappingBridgeGenerator] implementation which uses [SimpleBridgeGenerator] as the backend and
@@ -24,8 +27,7 @@ import org.jetbrains.kotlin.native.interop.indexer.*
*/
class MappingBridgeGeneratorImpl(
val declarationMapper: DeclarationMapper,
val simpleBridgeGenerator: SimpleBridgeGenerator,
val language: Language
val simpleBridgeGenerator: SimpleBridgeGenerator
) : MappingBridgeGenerator {
override fun kotlinToNative(
@@ -38,12 +40,7 @@ class MappingBridgeGeneratorImpl(
): KotlinExpression {
val bridgeArguments = mutableListOf<BridgeTypedKotlinValue>()
if (nativeBacked is FunctionStub && nativeBacked.isCxxInstanceMember()) {
bridgeArguments.add(BridgeTypedKotlinValue(BridgedType.NATIVE_PTR, "rawPtr"))
kotlinValues.drop(1)
} else {
kotlinValues
}.forEach { (type, value) ->
kotlinValues.forEach { (type, value) ->
if (type.unwrapTypedefs() is RecordType) {
builder.pushMemScoped()
val bridgeArgument = "$value.getPointer(memScope).rawValue"
@@ -82,25 +79,6 @@ class MappingBridgeGeneratorImpl(
val unwrappedType = type.unwrapTypedefs()
if (unwrappedType is RecordType) {
nativeValues.add("*(${unwrappedType.decl.spelling}*)${bridgeNativeValues[index]}")
} else if (language == Language.CPP) {
// C++ is more restrictive wrt type conversion
val cppRefTypePrefix = if (unwrappedType is PointerType && unwrappedType.isLVReference) "*" else ""
when { /// TODO Move this cludge to mirror()
type is Typedef ->
nativeValues.add("(${type.def.name})${bridgeNativeValues[index]}")
type is PointerType && type.spelling != null ->
nativeValues.add("(${type.spelling})$cppRefTypePrefix${bridgeNativeValues[index]}")
unwrappedType is EnumType ->
nativeValues.add("(${unwrappedType.def.spelling})${bridgeNativeValues[index]}")
unwrappedType is RecordType ->
nativeValues.add("*(${unwrappedType.decl.spelling}*)${bridgeNativeValues[index]}")
else ->
nativeValues.add(cppRefTypePrefix +
mirror(declarationMapper, type).info.cFromBridged(
bridgeNativeValues[index], scope, nativeBacked
)
)
}
} else {
nativeValues.add(
mirror(declarationMapper, type).info.cFromBridged(
@@ -112,27 +90,18 @@ class MappingBridgeGeneratorImpl(
val nativeResult = block(nativeValues)
when {
unwrappedReturnType is VoidType -> {
when (unwrappedReturnType) {
is VoidType -> {
out(nativeResult + ";")
""
}
unwrappedReturnType is RecordType -> {
is RecordType -> {
val kniStructResult = "kniStructResult"
if (language == Language.CPP) {
// use copy/move constructor to create object in place.
out("new(${bridgeNativeValues.last()}) ${unwrappedReturnType.decl.spelling}($nativeResult);")
} else {
out("${unwrappedReturnType.decl.spelling} $kniStructResult = $nativeResult;")
out("memcpy(${bridgeNativeValues.last()}, &$kniStructResult, sizeof($kniStructResult));")
// The following would be better, but won't work in case of const fields: C99 6.3.2.1p1
// out("*(${unwrappedReturnType.decl.spelling}*) ${bridgeNativeValues.last()} = $nativeResult;")
}
out("${unwrappedReturnType.decl.spelling} $kniStructResult = $nativeResult;")
out("memcpy(${bridgeNativeValues.last()}, &$kniStructResult, sizeof($kniStructResult));")
""
}
unwrappedReturnType is PointerType && unwrappedReturnType.isLVReference ->
"&$nativeResult"
else -> {
nativeResult
}
@@ -235,4 +204,4 @@ class MappingBridgeGeneratorImpl(
return result
}
}
}
@@ -25,6 +25,8 @@ interface DeclarationMapper {
fun getPackageFor(declaration: TypeDeclaration): String
val useUnsignedTypes: Boolean
fun getKotlinClassForManaged(structDecl: StructDecl): Classifier
}
fun DeclarationMapper.isMappedToSigned(integerType: IntegerType): Boolean = integerType.isSigned || !useUnsignedTypes
@@ -121,6 +123,18 @@ sealed class TypeMirror(val pointedType: KotlinClassifierType, val info: TypeInf
class ByRef(pointedType: KotlinClassifierType, info: TypeInfo) : TypeMirror(pointedType, info) {
override val argType: KotlinType get() = KotlinTypes.cValue.typeWith(pointedType)
}
/**
* Mirror for C++ Managed type.
*/
class Managed(
pointedType: KotlinClassifierType,
info: TypeInfo
) : TypeMirror(pointedType, info) {
override val argType: KotlinType
get() = pointedType
}
}
/**
@@ -399,11 +413,18 @@ private fun byRefTypeMirror(pointedType: KotlinClassifierType) : TypeMirror.ByRe
return TypeMirror.ByRef(pointedType, info)
}
private fun managedTypeMirror(pointedType: KotlinClassifierType) : TypeMirror.Managed {
val info = TypeInfo.ByRef(pointedType) // These are all errors anyways.
return TypeMirror.Managed(pointedType, info)
}
fun mirror(declarationMapper: DeclarationMapper, type: Type): TypeMirror = when (type) {
is PrimitiveType -> mirrorPrimitiveType(type, declarationMapper)
is RecordType -> byRefTypeMirror(declarationMapper.getKotlinClassForPointed(type.decl).type)
is ManagedType -> managedTypeMirror(declarationMapper.getKotlinClassForManaged(type.decl).type)
is EnumType -> {
val pkg = declarationMapper.getPackageFor(type.def)
val kotlinName = declarationMapper.getKotlinNameForValue(type.def)
@@ -489,6 +510,11 @@ fun mirror(declarationMapper: DeclarationMapper, type: Type): TypeMirror = when
Classifier.topLevel(pkg, name).typeAbbreviation(baseType.pointedType),
baseType.info
)
is TypeMirror.Managed -> TypeMirror.Managed(
Classifier.topLevel(pkg, name).typeAbbreviation(baseType.pointedType),
baseType.info
)
}
}
@@ -5,7 +5,7 @@ import org.jetbrains.kotlin.native.interop.indexer.*
fun tryRenderStructOrUnion(def: StructDef): String? = when (def.kind) {
StructDef.Kind.STRUCT -> tryRenderStruct(def)
StructDef.Kind.UNION -> tryRenderUnion(def)
StructDef.Kind.CLASS -> tryRenderStruct(def)
StructDef.Kind.CLASS -> null
}
private fun tryRenderStruct(def: StructDef): String? {
@@ -197,6 +197,7 @@ sealed class AnnotationStub(val classifier: Classifier) {
object CString : CCall(cCallClassifier.nested("CString"))
object WCString : CCall(cCallClassifier.nested("WCString"))
class Symbol(val symbolName: String) : CCall(cCallClassifier)
object CppClassConstructor : CCall(cCallClassifier.nested("CppClassConstructor"))
}
class CStruct(val struct: String) : AnnotationStub(cStructClassifier) {
@@ -207,6 +208,8 @@ sealed class AnnotationStub(val classifier: Classifier) {
class BitField(val offset: Long, val size: Int) : AnnotationStub(cStructClassifier.nested("BitField"))
class VarType(val size: Long, val align: Int) : AnnotationStub(cStructClassifier.nested("VarType"))
object ManagedType : AnnotationStub(cStructClassifier.nested("ManagedType"))
}
class CNaturalStruct(val members: List<StructMember>) :
@@ -63,7 +63,7 @@ class StubIrBridgeBuilder(
)
private val mappingBridgeGenerator: MappingBridgeGenerator =
MappingBridgeGeneratorImpl(declarationMapper, simpleBridgeGenerator, context.libraryForCStubs.language)
MappingBridgeGeneratorImpl(declarationMapper, simpleBridgeGenerator)
private val propertyAccessorBridgeBodies = mutableMapOf<PropertyAccessor, String>()
private val functionBridgeBodies = mutableMapOf<FunctionStub, List<String>>()
@@ -260,9 +260,6 @@ class StubIrBridgeBuilder(
isVararg = isVararg or parameter.isVararg
val parameterName = parameter.name.asSimpleName()
val bridgeArgument = when {
function.isCxxInstanceMember() && index == 0 -> {
"rawPtr"
}
parameter in builderResult.bridgeGenerationComponents.cStringParameters -> {
bodyGenerator.pushMemScoped()
"$parameterName?.cstr?.getPointer(memScope)"
@@ -292,18 +289,7 @@ class StubIrBridgeBuilder(
bridgeArguments,
independent = false
) { nativeValues ->
with (origin.function) {
when {
isCxxInstanceMethod ->
"(${nativeValues[0]})->${name}(${nativeValues.drop(1).joinToString()})"
isCxxConstructor ->
"new(${nativeValues[0]}) ${cxxReceiverClass!!.spelling}(${nativeValues.drop(1).joinToString()})"
isCxxDestructor ->
"(${nativeValues[0]})->~${cxxReceiverClass!!.spelling?.substringAfterLast(':')}()"
else ->
"${fullName}(${nativeValues.joinToString()})"
}
}
"${origin.function.name}(${nativeValues.joinToString()})"
}
bodyGenerator.returnResult(result)
functionBridgeBodies[function] = bodyGenerator.build()
@@ -124,7 +124,7 @@ interface StubsBuildingContext {
fun getKotlinClassForPointed(structDecl: StructDecl): Classifier
fun isOverloading(func: FunctionDecl): Boolean
fun isOverloading(name: String, types: List<StubType>): Boolean
}
/**
@@ -136,7 +136,7 @@ internal interface StubElementBuilder {
fun build(): List<StubIrElement>
}
class StubsBuildingContextImpl(
open class StubsBuildingContextImpl(
private val stubIrContext: StubIrContext
) : StubsBuildingContext {
@@ -144,12 +144,20 @@ class StubsBuildingContextImpl(
override val platform: KotlinPlatform = stubIrContext.platform
override val generationMode: GenerationMode = stubIrContext.generationMode
val imports: Imports = stubIrContext.imports
private val nativeIndex: NativeIndex = stubIrContext.nativeIndex
protected val nativeIndex: NativeIndex = stubIrContext.nativeIndex
private var theCounter = 0
private val uniqFunctions = mutableSetOf<String>()
override fun isOverloading(func: FunctionDecl) = !uniqFunctions.add(func.name) // TODO: params & return type.
override fun isOverloading(name: String, types: List<StubType>):Boolean {
return if (configuration.library.language == Language.CPP) {
val signature = "${name}( ${types.map { it.toString() }.joinToString(", ")} )"
!uniqFunctions.add(signature)
} else {
!uniqFunctions.add(name)
}
}
override fun generateNextUniqueId(prefix: String) =
prefix + pkgName.replace('.', '_') + theCounter++
@@ -181,34 +189,7 @@ class StubsBuildingContextImpl(
override val generatedObjCCategoriesMembers = mutableMapOf<ObjCClass, GeneratedObjCCategoriesMembers>()
override val declarationMapper = object : DeclarationMapper {
override fun getKotlinClassForPointed(structDecl: StructDecl): Classifier {
val baseName = structDecl.kotlinName
val pkg = when (platform) {
KotlinPlatform.JVM -> pkgName
KotlinPlatform.NATIVE -> if (structDecl.def == null) {
cnamesStructsPackageName // to be imported as forward declaration.
} else {
getPackageFor(structDecl)
}
}
return Classifier.topLevel(pkg, baseName)
}
override fun isMappedToStrict(enumDef: EnumDef): Boolean = isStrictEnum(enumDef)
override fun getKotlinNameForValue(enumDef: EnumDef): String = enumDef.kotlinName
override fun getPackageFor(declaration: TypeDeclaration): String {
return imports.getPackage(declaration.location) ?: pkgName
}
override val useUnsignedTypes: Boolean
get() = when (platform) {
KotlinPlatform.JVM -> false
KotlinPlatform.NATIVE -> true
}
}
override val declarationMapper = DeclarationMapperImpl()
override val macroConstantsByName: Map<String, MacroDef> =
(nativeIndex.macroConstants + nativeIndex.wrappedMacros).associateBy { it.name }
@@ -264,6 +245,39 @@ class StubsBuildingContextImpl(
val classifier = declarationMapper.getKotlinClassForPointed(structDecl)
return classifier
}
open inner class DeclarationMapperImpl : DeclarationMapper {
override fun getKotlinClassForPointed(structDecl: StructDecl): Classifier {
val baseName = structDecl.kotlinName
val pkg = when (platform) {
KotlinPlatform.JVM -> pkgName
KotlinPlatform.NATIVE -> if (structDecl.def == null) {
cnamesStructsPackageName // to be imported as forward declaration.
} else {
getPackageFor(structDecl)
}
}
return Classifier.topLevel(pkg, baseName)
}
override fun getKotlinClassForManaged(structDecl: StructDecl): Classifier =
error("ManagedType requires a plugin")
override fun isMappedToStrict(enumDef: EnumDef): Boolean = isStrictEnum(enumDef)
override fun getKotlinNameForValue(enumDef: EnumDef): String = enumDef.kotlinName
override fun getPackageFor(declaration: TypeDeclaration): String {
return imports.getPackage(declaration.location) ?: pkgName
}
override val useUnsignedTypes: Boolean
get() = when (platform) {
KotlinPlatform.JVM -> false
KotlinPlatform.NATIVE -> true
}
}
}
data class StubIrBuilderResult(
@@ -306,7 +320,7 @@ class StubIrBuilder(private val context: StubIrContext) {
private val excludedMacros: Set<String>
get() = configuration.excludedMacros
private val buildingContext = StubsBuildingContextImpl(context)
private val buildingContext = context.plugin.stubsBuildingContext(context)
fun build(): StubIrBuilderResult {
nativeIndex.objCProtocols.filter { !it.isForwardDeclaration }.forEach { generateStubsForObjCProtocol(it) }
@@ -8,6 +8,7 @@ import kotlinx.metadata.klib.KlibModuleMetadata
import org.jetbrains.kotlin.native.interop.gen.jvm.GenerationMode
import org.jetbrains.kotlin.native.interop.gen.jvm.InteropConfiguration
import org.jetbrains.kotlin.native.interop.gen.jvm.KotlinPlatform
import org.jetbrains.kotlin.native.interop.gen.jvm.Plugin
import org.jetbrains.kotlin.native.interop.indexer.*
import java.io.File
import java.util.*
@@ -19,7 +20,8 @@ class StubIrContext(
val imports: Imports,
val platform: KotlinPlatform,
val generationMode: GenerationMode,
val libName: String
val libName: String,
val plugin: Plugin
) {
val libraryForCStubs = configuration.library.copy(
includes = mutableListOf<String>().apply {
@@ -109,7 +111,8 @@ class StubIrDriver(
val entryPoint: String?,
val moduleName: String,
val outCFile: File,
val outKtFileCreator: () -> File
val outKtFileCreator: () -> File,
val dumpBridges: Boolean
)
sealed class Result {
@@ -128,6 +131,13 @@ class StubIrDriver(
emitCFile(context, it, entryPoint, bridgeBuilderResult.nativeBridges)
}
if (options.dumpBridges) {
context.log("GENERATED KOTLIN: ${bridgeBuilderResult.nativeBridges.kotlinLines.toList().size}")
bridgeBuilderResult.nativeBridges.kotlinLines.forEach { context.log(it) }
context.log("GENERATED NATIVE: ${bridgeBuilderResult.nativeBridges.nativeLines.toList().size}")
bridgeBuilderResult.nativeBridges.nativeLines.forEach { context.log(it) }
}
return when (context.generationMode) {
GenerationMode.SOURCE_CODE -> {
emitSourceCode(outKtFile(), builderResult, bridgeBuilderResult)
@@ -78,14 +78,25 @@ internal class StructStubBuilder(
AnnotationStub.CStruct(it)
}
}
val managedAnnotation = if (context.configuration.library.language == Language.CPP
&& def.kind == StructDef.Kind.CLASS) {
AnnotationStub.CStruct.ManagedType
} else {
null
}
val structAnnotations = listOfNotNull(structAnnotation, managedAnnotation)
val classifier = context.getKotlinClassForPointed(decl)
var methods: List<FunctionStub> =
val methods: List<FunctionStub> =
def.methods
.filter { it.isCxxInstanceMethod }
// TODO: this excludes all similar named methods from all calsses.
// Consider using fqnames or something.
.filterNot { it.name in context.configuration.excludedFunctions }
.map { func ->
try {
(FunctionStubBuilder(context, func).build() as List<FunctionStub>).single()
(FunctionStubBuilder(context, func, skipOverloads = true).build().map { it as FunctionStub }).single()
} catch (e: Throwable) {
null
}
@@ -191,18 +202,52 @@ internal class StructStubBuilder(
context.generationMode == GenerationMode.METADATA
}
var classMethods: List<FunctionStub> =
val classMethods: List<FunctionStub> =
def.methods
.filter { !it.isCxxInstanceMethod }
.map { func ->
try {
(FunctionStubBuilder(context, func).build() as List<FunctionStub>).single()
FunctionStubBuilder(context, func, skipOverloads = true).build().map { it as FunctionStub }.single()
} catch (e: Throwable) {
null
}
}.filterNotNull()
// Here's what we have for C++.
// Note that we account for constructors twice.
// class XXX {
// // These go into `methods`
// foo()
// bar(x, y)
//
// // These are in the `secondaryConstructors` variable.
// // their signatures match the signatures of __init__ modulo `self` parameters.
// // The primary constructor will be created for the class the same way as for interop structs.
// constructor(z)
// constructor(t, u)
//
// Companion {
// // These all go to `classMethods`
// __init__(self, z)
// __init__(self, t, u)
// __destroy__(self)
// aStaticMathod()
// }
// }
val secondaryConstructors: List<ConstructorStub> =
def.methods
.filter { it.isCxxConstructor }
.map { func ->
try {
ConstructorStubBuilder(context, func).build().map { it as ConstructorStub }.single()
} catch (e: Throwable) {
null
}
}.filterNotNull()
val classFields = def.staticFields
.map { field -> (GlobalStubBuilder(context, field).build() as List<PropertyStub>).single() }
.map { field -> (GlobalStubBuilder(context, field).build().map{ it as PropertyStub }).single() }
val companion = ClassStub.Companion(
companionClassifier,
@@ -215,10 +260,10 @@ internal class StructStubBuilder(
classifier,
origin = origin,
properties = fields.filterNotNull() + if (platform == KotlinPlatform.NATIVE) bitFields else emptyList(),
constructors = listOf(primaryConstructor),
constructors = listOf(primaryConstructor) + secondaryConstructors,
methods = methods,
modality = ClassStubModality.NONE,
annotations = listOfNotNull(structAnnotation),
annotations = structAnnotations,
superClassInit = superClassInit,
companion = companion
))
@@ -491,18 +536,22 @@ internal class EnumStubBuilder(
}
}
internal class FunctionStubBuilder(
internal abstract class FunctionalStubBuilder(
override val context: StubsBuildingContext,
private val func: FunctionDecl,
private val skipOverloads: Boolean = false
protected val func: FunctionDecl,
protected val skipOverloads: Boolean = false
) : StubElementBuilder {
override fun build(): List<StubIrElement> {
val platform = context.platform
val parameters = mutableListOf<FunctionParameterStub>()
abstract override fun build(): List<StubIrElement>
fun buildParameters(parameters: MutableList<FunctionParameterStub>, platform: KotlinPlatform): Boolean {
var hasStableParameterNames = true
func.parameters.forEachIndexed { index, parameter ->
val funcParameters = if (func.isCxxInstanceMethod) {
func.parameters.drop(1)
} else {
func.parameters
}
funcParameters.forEachIndexed { index, parameter ->
val parameterName = parameter.name.let {
if (it == null || it.isEmpty()) {
hasStableParameterNames = false
@@ -544,45 +593,13 @@ internal class FunctionStubBuilder(
}
}
}
val returnType = if (func.returnsVoid()) {
KotlinTypes.unit
} else {
context.mirror(func.returnType).argType
}.toStubIrType()
if (skipOverloads && context.isOverloading(func))
return emptyList()
val annotations: List<AnnotationStub>
val mustBeExternal: Boolean
if (platform == KotlinPlatform.JVM) {
annotations = emptyList()
mustBeExternal = false
} else {
if (func.isVararg) {
val type = KotlinTypes.any.makeNullable().toStubIrType()
parameters += FunctionParameterStub("variadicArguments", type, isVararg = true)
}
annotations = listOf(AnnotationStub.CCall.Symbol("${context.generateNextUniqueId("knifunptr_")}_${func.name}"))
mustBeExternal = true
}
val functionStub = FunctionStub(
func.name,
returnType,
parameters.toList(),
StubOrigin.Function(func),
annotations,
mustBeExternal,
null,
MemberStubModality.FINAL,
hasStableParameterNames = hasStableParameterNames
)
return listOf(functionStub)
return hasStableParameterNames
}
protected fun buildFunctionAnnotations(func: FunctionDecl, stubName: String = func.name) =
listOf(AnnotationStub.CCall.Symbol("${context.generateNextUniqueId("knifunptr_")}_${stubName}"))
private fun FunctionDecl.returnsVoid(): Boolean = this.returnType.unwrapTypedefs() is VoidType
protected fun FunctionDecl.returnsVoid(): Boolean = this.returnType.unwrapTypedefs() is VoidType
private fun representCFunctionParameterAsValuesRef(type: Type): KotlinType? {
val pointeeType = when (type) {
@@ -638,6 +655,105 @@ internal class FunctionStubBuilder(
&& !noStringConversion.contains(function.name)
}
internal class FunctionStubBuilder(
context: StubsBuildingContext,
func: FunctionDecl,
skipOverloads: Boolean = false
) : FunctionalStubBuilder(context, func, skipOverloads) {
override fun build(): List<StubIrElement> {
val platform = context.platform
val parameters = mutableListOf<FunctionParameterStub>()
val hasStableParameterNames = buildParameters(parameters, platform)
val returnType = when {
func.returnsVoid() -> KotlinTypes.unit
else -> context.mirror(func.returnType).argType
}.toStubIrType()
if (skipOverloads && context.isOverloading(func.fullName, parameters.map { it.type }))
return emptyList()
val annotations: List<AnnotationStub>
val mustBeExternal: Boolean
if (platform == KotlinPlatform.JVM) {
annotations = emptyList()
mustBeExternal = false
} else {
if (func.isVararg) {
val type = KotlinTypes.any.makeNullable().toStubIrType()
parameters += FunctionParameterStub("variadicArguments", type, isVararg = true)
}
annotations = buildFunctionAnnotations(func)
mustBeExternal = true
}
val name = if (context.configuration.library.language == Language.CPP && !func.isCxxMethod) {
func.fullName
} else {
func.name
}
val functionStub = FunctionStub(
name,
returnType,
parameters.toList(),
StubOrigin.Function(func),
annotations,
mustBeExternal,
null,
MemberStubModality.FINAL,
hasStableParameterNames = hasStableParameterNames
)
return listOf(functionStub)
}
}
internal class ConstructorStubBuilder(
context: StubsBuildingContext,
func: FunctionDecl,
skipOverloads: Boolean = false
) : FunctionalStubBuilder(context, func, skipOverloads) {
override fun build(): List<StubIrElement> {
if (context.configuration.library.language != Language.CPP) return emptyList() // TODO: Should we assert here?
val platform = context.platform
val parameters = mutableListOf<FunctionParameterStub>()
val name = func.parentName ?: return emptyList()
buildParameters(parameters, platform)
// We build it on the basis of "__init__" member, so drop the "placement" argugment.
parameters.removeFirst()
if (skipOverloads && context.isOverloading(func.fullName, parameters.map { it.type }))
return emptyList()
val annotations =
if (platform == KotlinPlatform.JVM) {
emptyList()
} else {
if (func.isVararg) {
val type = KotlinTypes.any.makeNullable().toStubIrType()
parameters += FunctionParameterStub("variadicArguments", type, isVararg = true)
}
buildFunctionAnnotations(func, name) + AnnotationStub.CCall.CppClassConstructor
}
val result = ConstructorStub(
parameters,
annotations,
isPrimary = false,
origin = StubOrigin.Function(func),
)
return listOf(result)
}
}
internal class GlobalStubBuilder(
override val context: StubsBuildingContext,
private val global: GlobalDecl
@@ -719,6 +835,7 @@ internal class GlobalStubBuilder(
}
kind = PropertyStub.Kind.Val(getter)
}
is TypeMirror.Managed -> error("We don't support managed globals for now")
}
}
return listOf(PropertyStub(global.name, kotlinType.toStubIrType(), kind, origin = origin))
@@ -748,6 +865,10 @@ internal class TypedefStubBuilder(
val varTypeAliasee = baseMirror.pointedType
listOf(TypealiasStub(varType, varTypeAliasee.toStubIrType(), origin))
}
is TypeMirror.Managed -> {
val varTypeAliasee = baseMirror.pointedType
listOf(TypealiasStub(varType, varTypeAliasee.toStubIrType(), origin))
}
}
}
}
@@ -12,17 +12,6 @@ private val StubOrigin.ObjCMethod.isOptional: Boolean
fun FunctionStub.isOptionalObjCMethod(): Boolean = this.origin is StubOrigin.ObjCMethod &&
this.origin.isOptional
fun FunctionStub.isCxxInstanceMember(): Boolean = this.origin is StubOrigin.Function &&
this.origin.function.isCxxInstanceMethod
fun FunctionStub.qualifiedName(): String =
if (this.origin is StubOrigin.Function && !this.origin.function.isCxxMethod) {
this.origin.function.fullName
} else {
name
}
val StubContainer.isInterface: Boolean
get() = if (this is ClassStub.Simple) {
modality == ClassStubModality.INTERFACE
@@ -413,6 +413,7 @@ private class MappingExtensions(
is AnnotationStub.CCall.Symbol -> mapOfNotNull(
("id" to symbolName).asAnnotationArgument()
)
is AnnotationStub.CCall.CppClassConstructor -> emptyMap()
is AnnotationStub.CStruct -> mapOfNotNull(
("spelling" to struct).asAnnotationArgument()
)
@@ -446,6 +447,7 @@ private class MappingExtensions(
("size" to KmAnnotationArgument.LongValue(size)),
("align" to KmAnnotationArgument.IntValue(align))
)
is AnnotationStub.CStruct.ManagedType -> emptyMap()
}
return KmAnnotation(classifier.fqNameSerialized, args)
}
@@ -184,8 +184,7 @@ class StubIrTextEmitter(
if (element in bridgeBuilderResult.excludedStubs) return
val header = run {
val parameters = (if (element.isCxxInstanceMember()) element.parameters.drop(1) else element.parameters).
joinToString(prefix = "(", postfix = ")") { renderFunctionParameter(it) }
val parameters = element.parameters.joinToString(prefix = "(", postfix = ")") { renderFunctionParameter(it) }
val receiver = element.receiver?.let { renderFunctionReceiver(it) + "." } ?: ""
val typeParameters = renderTypeParameters(element.typeParameters)
val override = if (element.isOverride) "override " else ""
@@ -485,10 +484,14 @@ class StubIrTextEmitter(
"@CCall.WCString"
is AnnotationStub.CCall.Symbol ->
"@CCall(${annotationStub.symbolName.quoteAsKotlinLiteral()})"
AnnotationStub.CCall.CppClassConstructor ->
"@CCall.CppClassConstructor"
is AnnotationStub.CStruct ->
"@CStruct(${annotationStub.struct.quoteAsKotlinLiteral()})"
is AnnotationStub.CNaturalStruct ->
"@CNaturalStruct(${annotationStub.members.joinToString { it.name.quoteAsKotlinLiteral() }})"
is AnnotationStub.CStruct.ManagedType ->
"@CStruct.ManagedType"
is AnnotationStub.CLength ->
"@CLength(${annotationStub.length})"
is AnnotationStub.Deprecated ->
@@ -16,6 +16,8 @@
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.gen.jvm.DefaultPlugin
import org.jetbrains.kotlin.native.interop.gen.jvm.Plugin
import org.jetbrains.kotlin.native.interop.indexer.*
val EnumDef.isAnonymous: Boolean
@@ -30,10 +32,10 @@ val StructDecl.isAnonymous: Boolean
*
* TODO: use libclang to implement?
*/
fun Type.getStringRepresentation(): String = when (this) {
fun Type.getStringRepresentation(plugin: Plugin = DefaultPlugin): String = when (this) {
VoidType -> "void"
CharType -> "char"
CBoolType -> "_Bool"
CBoolType -> if (plugin.name == "Skia") "bool" else "_Bool"
ObjCBoolType -> "BOOL"
is IntegerType -> this.spelling
is FloatingType -> this.spelling
@@ -50,7 +52,7 @@ fun Type.getStringRepresentation(): String = when (this) {
this.def.spelling
}
is Typedef -> this.def.aliased.getStringRepresentation()
is Typedef -> this.def.aliased.getStringRepresentation(plugin)
is ObjCPointer -> when (this) {
is ObjCIdType -> "id$protocolQualifier"
@@ -60,6 +62,8 @@ fun Type.getStringRepresentation(): String = when (this) {
is ObjCBlockPointer -> "id"
}
is ManagedType -> with(plugin) { this@getStringRepresentation.stringRepresentation }
else -> throw NotImplementedError()
}
@@ -31,6 +31,7 @@ const val NOPACK = "nopack"
const val COMPILE_SOURCES = "Xcompile-source"
const val SHORT_MODULE_NAME = "Xshort-module-name"
const val FOREIGN_EXCEPTION_MODE = "Xforeign-exception-mode"
const val DUMP_BRIDGES = "Xdump-bridges"
// TODO: unify camel and snake cases.
// Possible solution is to accept both cases
@@ -121,6 +122,9 @@ open class CInteropArguments(argParser: ArgParser =
val foreignExceptionMode by argParser.option(ArgType.String, FOREIGN_EXCEPTION_MODE,
description = "Handle native exception in Kotlin: <terminate|objc-wrap>")
val dumpBridges by argParser.option(ArgType.Boolean, DUMP_BRIDGES,
description = "Dump generated bridges")
}
class JSInteropArguments(argParser: ArgParser = ArgParser("jsinterop",
@@ -0,0 +1,35 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.native.interop.gen.jvm
import org.jetbrains.kotlin.native.interop.gen.*
import org.jetbrains.kotlin.native.interop.indexer.*
object Plugins {
fun plugin(pluginName: String?): Plugin = when (pluginName) {
"org.jetbrains.kotlin.native.interop.skia" ->
Class.forName("$pluginName.SkiaPlugin").newInstance() as Plugin
null -> DefaultPlugin
else -> error("Unexected interop plugin: $pluginName")
}
}
interface Plugin {
val name: String
fun buildNativeIndex(library: NativeLibrary, verbose: Boolean): IndexerResult
val managedTypePassing: ManagedTypePassing
val ManagedType.stringRepresentation: String
fun stubsBuildingContext(stubIrContext: StubIrContext): StubsBuildingContext
}
object DefaultPlugin : Plugin {
override val name = "Default"
override fun buildNativeIndex(library: NativeLibrary, verbose: Boolean): IndexerResult =
buildNativeIndexImpl(library, verbose)
override val managedTypePassing = ManagedTypePassing()
override val ManagedType.stringRepresentation get() = error("ManagedType requires non-default interop plugin")
override fun stubsBuildingContext(stubIrContext: StubIrContext) = StubsBuildingContextImpl(stubIrContext)
}
@@ -102,6 +102,7 @@ private fun List<String>?.isTrue(): Boolean {
}
private fun runCmd(command: Array<String>, verbose: Boolean = false) {
if (verbose) println("COMMAND: " + command.joinToString(" "))
Command(*command).getOutputLines(true).let { lines ->
if (verbose) lines.forEach(::println)
}
@@ -121,19 +122,22 @@ private fun Properties.putAndRunOnReplace(key: Any, newValue: Any, beforeReplace
this[key] = newValue
}
private fun selectNativeLanguage(config: DefFile.DefFileConfig, hintIsCPP: Boolean = false): Language {
private fun selectNativeLanguage(config: DefFile.DefFileConfig): Language {
val languages = mapOf(
"C" to Language.C,
"C++" to Language.CPP,
"Objective-C" to Language.OBJECTIVE_C
)
// C++ is not publicly supported.
val publicLanguages = languages.keys.minus("C++")
val lang = config.language?.let {
languages[it]
?: error("Unexpected language '${config.language}'. Possible values are: ${languages.keys.joinToString { "'$it'" }}")
?: error("Unexpected language '${config.language}'. Possible values are: ${publicLanguages.joinToString { "'$it'" }}")
} ?: Language.C
return if (lang == Language.C && hintIsCPP) Language.CPP else lang
return lang
}
@@ -267,7 +271,9 @@ private fun processCLib(flavor: KotlinPlatform, cinteropArguments: CInteropArgum
val library = buildNativeLibrary(tool, def, cinteropArguments, imports)
val (nativeIndex, compilation) = buildNativeIndex(library, verbose)
val plugin = Plugins.plugin(def.config.pluginName)
val (nativeIndex, compilation) = plugin.buildNativeIndex(library, verbose)
// Our current approach to arm64_32 support is to compile armv7k version of bitcode
// for arm64_32. That's the reason for this substitution.
@@ -304,7 +310,7 @@ private fun processCLib(flavor: KotlinPlatform, cinteropArguments: CInteropArgum
{}
}
val stubIrContext = StubIrContext(logger, configuration, nativeIndex, imports, flavor, mode, libName)
val stubIrContext = StubIrContext(logger, configuration, nativeIndex, imports, flavor, mode, libName, plugin)
val stubIrOutput = run {
val outKtFileCreator = {
val outKtFileName = fqParts.last() + ".kt"
@@ -313,7 +319,13 @@ private fun processCLib(flavor: KotlinPlatform, cinteropArguments: CInteropArgum
file.parentFile.mkdirs()
file
}
val driverOptions = StubIrDriver.DriverOptions(entryPoint, moduleName, File(outCFile.absolutePath), outKtFileCreator)
val driverOptions = StubIrDriver.DriverOptions(
entryPoint,
moduleName,
File(outCFile.absolutePath),
outKtFileCreator,
cinteropArguments.dumpBridges ?: false
)
val stubIrDriver = StubIrDriver(stubIrContext, driverOptions)
stubIrDriver.run()
}
@@ -330,7 +342,6 @@ private fun processCLib(flavor: KotlinPlatform, cinteropArguments: CInteropArgum
def.manifestAddendProperties["ir_provider"] = KLIB_INTEROP_IR_PROVIDER_IDENTIFIER
}
stubIrContext.addManifestProperties(def.manifestAddendProperties)
// cinterop command line option overrides def file property
val foreignExceptionMode = cinteropArguments.foreignExceptionMode?: def.config.foreignExceptionMode
foreignExceptionMode?.let {
@@ -360,7 +371,6 @@ private fun processCLib(flavor: KotlinPlatform, cinteropArguments: CInteropArgum
val outLib = File(nativeLibsDir, "$libName.bc")
val compilerCmd = arrayOf(compiler, *compilerArgs,
"-emit-llvm", "-c", outCFile.absolutePath, "-o", outLib.absolutePath)
runCmd(compilerCmd, verbose)
outLib.absolutePath
}
@@ -453,14 +463,6 @@ internal fun prepareTool(target: String?, flavor: KotlinPlatform): ToolConfig {
return tool
}
private fun isCxxOptions(opts: List<String>) : Boolean {
if (opts.size >= 2) opts.reduce args@{ prev, that ->
if (prev == "-x" && that == "c++") return@isCxxOptions true
return@args that
}
return false
}
internal fun buildNativeLibrary(
tool: ToolConfig,
def: DefFile,
@@ -472,8 +474,7 @@ internal fun buildNativeLibrary(
arguments.compilerOptions + arguments.compilerOption).toTypedArray()
val headerFiles = def.config.headers + additionalHeaders
val cppOptions = isCxxOptions(def.config.compilerOpts + additionalCompilerOpts)
val language = selectNativeLanguage(def.config, cppOptions)
val language = selectNativeLanguage(def.config)
val compilerOpts: List<String> = mutableListOf<String>().apply {
addAll(def.config.compilerOpts)
addAll(tool.defaultCompilerOpts)
@@ -487,7 +488,7 @@ internal fun buildNativeLibrary(
addAll(getCompilerFlagsForVfsOverlay(arguments.headerFilterPrefix.toTypedArray(), def))
addAll(when (language) {
Language.C -> emptyList()
Language.CPP -> if (cppOptions) emptyList() else listOf("-x", "c++")
Language.CPP -> emptyList()
Language.OBJECTIVE_C -> {
// "Objective-C" within interop means "Objective-C with ARC":
listOf("-fobjc-arc")