[Kotlin/Native][Interop] Provide pure c wrappers over cpp for skia interop

This commit is contained in:
Vladimir Ivanov
2019-08-19 11:44:20 +03:00
committed by Alexander Gorshenev
parent 61825e9aec
commit 5f582ad28a
31 changed files with 1525 additions and 66 deletions
@@ -32,6 +32,8 @@ private class StructDefImpl(
size, align, decl
) {
override val members = mutableListOf<StructMember>()
override val methods = mutableListOf<FunctionDecl>()
override val staticFields = mutableListOf<GlobalDecl>()
}
private class EnumDefImpl(spelling: String, type: Type, override val location: Location) : EnumDef(spelling, type) {
@@ -74,6 +76,30 @@ private class ObjCCategoryImpl(
override val properties = mutableListOf<ObjCProperty>()
}
private fun getParentName(cursor: CValue<CXCursor>, pkg: List<String> = emptyList()) : String? { // }: List<String>? {
// This doesn't work for anonymous C++ struct (such as typedef struct { void foo(); } TypeDefName) as well as anon namespace
// In contrast, clang_getTypeSpelling return fully qualified name for struct & class (incl. typedef anon struct),
// but does not help for anything elde such as template member, namespace etc
// So, TODO Use ultimately clang_getTypeSpelling for CXType_Record (no traversing needed) and traverse up the whole hierarchy for anythiong else
// Unfortunately, this won't work too for variable decl with anon type like that: ''struct { void foo(); } x;''
// while function is accessible as x.foo()
// skip this (zero) level:
val parent = clang_getCursorSemanticParent(cursor)
if (clang_isDeclaration(parent.kind) == 0)
return if (pkg.isNotEmpty()) pkg.joinToString("::") else null
val type = clang_getCursorType(parent)
if (type.kind == CXTypeKind.CXType_Record)
return clang_getTypeSpelling(type).convertAndDispose()
val nextPkg = if (parent.kind == CXCursorKind.CXCursor_Namespace) listOf(parent.spelling) + pkg else pkg
return getParentName(parent, nextPkg)
}
internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean = false) : NativeIndex() {
private sealed class DeclarationID {
@@ -136,10 +162,10 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
override val typedefs get() = typedefRegistry.included
private val typedefRegistry = TypeDeclarationRegistry<TypedefDef>()
private val functionById = mutableMapOf<DeclarationID, FunctionDecl>()
private val functionById = mutableMapOf<DeclarationID, FunctionDecl?>()
override val functions: Collection<FunctionDecl>
get() = functionById.values
get() = functionById.values.filterNotNull()
override val macroConstants = mutableListOf<ConstantDef>()
override val wrappedMacros = mutableListOf<WrappedMacroDef>()
@@ -191,6 +217,43 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
return StructDeclImpl(typeSpelling, getLocation(cursor))
}
private fun visitClass(cursor: CValue<CXCursor>, clazz: StructDefImpl) {
// TODO skip method (function) when encounter UnsupportedType in params or ret value. Otherwise all class methods will be lost due to exception (?)
visitChildren(cursor) { cursor, _ ->
if (cursor.isPublic) {
// TODO If a kotlin class is _conceptually_ derived from its c++ counterpart, then it shall be able to override virtual private and access protected
when (cursor.kind) {
CXCursorKind.CXCursor_CXXMethod -> {
val isOperatorFunction = (clang_getCursorSpelling(cursor).convertAndDispose().take(8) == "operator")
// operators are Not Implemented Yet
if (!isOperatorFunction) {
if (clang_isFunctionTypeVariadic(clang_getCursorType(cursor)) == 0) // FIXME why it doesn't work???
getFunction(cursor, clazz.decl)?.let { clazz.methods.add(it) }
}
}
CXCursorKind.CXCursor_Constructor ->
getFunction(cursor, clazz.decl)?.let { clazz.methods.add(it) }
CXCursorKind.CXCursor_Destructor ->
getFunction(cursor, clazz.decl)?.let { clazz.methods.add(it) }
CXCursorKind.CXCursor_VarDecl -> {
clazz.staticFields.add(GlobalDecl(
name =getCursorSpelling(cursor),
type = convertCursorType(cursor),
isConst = clang_isConstQualifiedType(clang_getCursorType(cursor)) != 0,
parentName = clazz.decl.spelling)
)
}
else -> {
}
}
}
CXChildVisitResult.CXChildVisit_Continue
}
}
private fun createStructDef(structDecl: StructDeclImpl, cursor: CValue<CXCursor>) {
val type = clang_getCursorType(cursor)
@@ -205,17 +268,19 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
when (cursor.kind) {
CXCursorKind.CXCursor_UnionDecl -> StructDef.Kind.UNION
CXCursorKind.CXCursor_StructDecl -> StructDef.Kind.STRUCT
CXCursorKind.CXCursor_ClassDecl -> StructDef.Kind.CLASS
else -> error(cursor.kind)
}
)
structDef.members += fields
visitClass(cursor, structDef)
structDecl.def = structDef
}
private fun addDeclaredFields(result: MutableList<StructMember>, structType: CValue<CXType>, containerType: CValue<CXType>) {
getFields(containerType).forEach { fieldCursor ->
getFields(containerType).filter { it.isPublic }.forEach { fieldCursor ->
val name = getCursorSpelling(fieldCursor)
if (name.isNotEmpty()) {
val fieldType = convertCursorType(fieldCursor)
@@ -465,7 +530,7 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
convertType(clang_getCursorType(cursor), clang_getDeclTypeAttributes(cursor))
private inline fun objCType(supplier: () -> ObjCPointer) = when (library.language) {
Language.C -> UnsupportedType
Language.C, Language.CPP -> UnsupportedType
Language.OBJECTIVE_C -> supplier()
}
@@ -582,7 +647,7 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
CXType_Record -> RecordType(getStructDeclAt(clang_getTypeDeclaration(type)))
CXType_Enum -> EnumType(getEnumDefAt(clang_getTypeDeclaration(type)))
CXType_Pointer -> {
CXType_Pointer, CXType_LValueReference -> {
val pointeeType = clang_getPointeeType(type)
val pointeeIsConst =
(clang_isConstQualifiedType(clang_getCanonicalType(pointeeType)) != 0)
@@ -590,7 +655,9 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
val convertedPointeeType = convertType(pointeeType)
PointerType(
if (convertedPointeeType == UnsupportedType) VoidType else convertedPointeeType,
pointeeIsConst = pointeeIsConst
pointeeIsConst = pointeeIsConst,
isLVReference = (kind == CXType_LValueReference),
spelling = type.name
)
}
@@ -794,23 +861,43 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
return
}
val namespace: Namespace? =
// semantic parent of any namespace member is always namespace itself (no type aliases etc)
if (info.semanticContainer!!.pointed.cursor.kind == CXCursorKind.CXCursor_Namespace) {
val parent = info.semanticContainer!!.pointed.cursor.readValue()
Namespace(getCursorSpelling(parent), getParentName(parent))
} else null
if (!cursor.isRecursivelyPublic()) {
// c++ : skip anon namespaces, static functions and variables and private inner classes
return
}
/**
* TODO It may be better to look at CXTypeKind instead of CXIdxEntity to distinguish C++ classes from templates
* C++ templates are also CXIdxEntity_CXXClass but CXCursor_ClassTemplate,
* while C++ class is CXCursor_ClassDecl
* The same for CXCursor_FunctionDecl vs CXCursor_FunctionTemplate
*/
when (kind) {
CXIdxEntity_Struct, CXIdxEntity_Union -> {
CXIdxEntity_Struct, CXIdxEntity_Union, CXIdxEntity_CXXClass -> {
if (entityName == null) {
// Skip anonymous struct.
// (It gets included anyway if used as a named field type).
} else {
getStructDeclAt(cursor)
if (library.language != Language.CPP) {
getStructDeclAt(cursor)
}
}
}
CXIdxEntity_Typedef -> {
CXIdxEntity_Typedef, CXIdxEntity_CXXTypeAlias -> {
val type = clang_getCursorType(cursor)
getTypedef(type)
}
CXIdxEntity_Function -> {
if (isSuitableFunction(cursor)) {
if (isSuitableFunction(cursor)
&& library.language != Language.CPP) {
functionById.getOrPut(getDeclarationId(cursor)) {
getFunction(cursor)
}
@@ -822,13 +909,15 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
}
CXIdxEntity_Variable -> {
if (info.semanticContainer!!.pointed.cursor.kind == CXCursorKind.CXCursor_TranslationUnit) {
// Top-level variable.
val parentKind = info.semanticContainer!!.pointed.cursor.kind
if (parentKind == CXCursorKind.CXCursor_TranslationUnit || parentKind == CXCursorKind.CXCursor_Namespace) {
// Top-level or namespace member. Skip class static members - they are loaded by visitClass
globalById.getOrPut(getDeclarationId(cursor)) {
GlobalDecl(
name = entityName!!,
type = convertCursorType(cursor),
isConst = clang_isConstQualifiedType(clang_getCursorType(cursor)) != 0
isConst = clang_isConstQualifiedType(clang_getCursorType(cursor)) != 0,
parentName = getParentName(cursor)
)
}
}
@@ -880,6 +969,49 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
}
}
fun indexDeclaration(cursor: CValue<CXCursor>): Unit {
if (!library.includesDeclaration(cursor)) {
return
}
if (cursor.isRecursivelyPublic()) {
when (cursor.kind) {
CXCursorKind.CXCursor_ClassDecl, CXCursorKind.CXCursor_StructDecl, CXCursorKind.CXCursor_UnionDecl -> {
if (library.language == Language.CPP) {
if (cursor.spelling.isEmpty()) {
// Skip anonymous struct.
// (It gets included anyway if used as a named field type).
} else {
getStructDeclAt(cursor)
}
}
}
CXCursorKind.CXCursor_FunctionDecl -> {
if (library.language == Language.CPP) {
indexCxxFunction(cursor)
}
}
else -> {
}
}
}
}
private fun indexCxxFunction(cursor: CValue<CXCursor>) {
if (isSuitableFunction(cursor)) {
if (getCursorSpelling(cursor).take(8) == "operator") {
// not implemented yet
} else {
functionById.getOrPut(getDeclarationId(cursor)) {
getFunction(cursor)
}
}
}
}
fun indexObjCClass(cursor: CValue<CXCursor>) {
if (isAvailable(cursor)) {
getObjCClassAt(cursor)
@@ -892,14 +1024,19 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
}
}
private fun getFunction(cursor: CValue<CXCursor>): FunctionDecl {
val name = clang_getCursorSpelling(cursor).convertAndDispose()
val returnType = convertType(clang_getCursorResultType(cursor), clang_getCursorResultTypeAttributes(cursor))
private fun getFunction(cursor: CValue<CXCursor>, receiver: StructDecl? = null): FunctionDecl? {
if (!isFuncDeclEligible(cursor)) {
log("Skip function ${clang_getCursorSpelling(cursor).convertAndDispose()}")
return null
}
var name = clang_getCursorSpelling(cursor).convertAndDispose()
var returnType = convertType(clang_getCursorResultType(cursor), clang_getCursorResultTypeAttributes(cursor))
val parameters = getFunctionParameters(cursor)
val parameters = mutableListOf<Parameter>()
parameters += getFunctionParameters(cursor)
val binaryName = when (library.language) {
Language.C, Language.OBJECTIVE_C -> clang_Cursor_getMangling(cursor).convertAndDispose()
Language.C, Language.CPP, Language.OBJECTIVE_C -> clang_Cursor_getMangling(cursor).convertAndDispose()
}
val definitionCursor = clang_getCursorDefinition(cursor)
@@ -907,7 +1044,40 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
val isVararg = clang_Cursor_isVariadic(cursor) != 0
return FunctionDecl(name, parameters, returnType, binaryName, isDefined, isVararg)
// TODO Do the following if clang_getCursorLanguage(cursor) == CXLanguageKind.CXLanguage_CPlusPlus ...
val parentName = getParentName(cursor)
val cxxMethodInfo = receiver?.let { CxxMethodInfo(
PointerType(RecordType(receiver),
clang_CXXMethod_isConst(cursor) != 0), // CXCursor_ConversionFunction has constness too
when (cursor.kind) {
CXCursorKind.CXCursor_Constructor -> {
returnType = PointerType(RecordType(receiver))
name = "__init__" // It is intended to init preallocated memory with placement new, so it is not "create" factory method. TODO One may want "create" method also.
// Parameter type for placement new is void*, but I want to emphasize that memory block ahall have proper size and alignment
parameters.add(0, Parameter("self", PointerType(RecordType(receiver)), false))
CxxMethodKind.Constructor
}
CXCursorKind.CXCursor_Destructor -> {
name = "__destroy__"
parameters.add(0, Parameter("self", PointerType(RecordType(receiver)), false))
CxxMethodKind.Destructor
}
// CXCursorKind.CXCursor_ConversionFunction -> ...
CXCursorKind.CXCursor_CXXMethod ->
if (clang_CXXMethod_isStatic(cursor) != 0) {
CxxMethodKind.StaticMethod
} else {
parameters.add(0, Parameter("self",
PointerType(RecordType(receiver), clang_CXXMethod_isConst(cursor) != 0),
false))
CxxMethodKind.InstanceMethod
}
else -> CxxMethodKind.None // Not implemented. Not expected, OK to assert (?)
}
)
}
return FunctionDecl(name, parameters, returnType, binaryName, isDefined, isVararg, parentName, cxxMethodInfo)
}
private fun getObjCMethod(cursor: CValue<CXCursor>): ObjCMethod? {
@@ -957,6 +1127,22 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
CXAvailabilityKind.CXAvailability_NotAccessible -> false
}
// Skip functions which parameter or return type is TemplateRef
private fun isFuncDeclEligible(cursor: CValue<CXCursor>): Boolean {
var ret = true
visitChildren(cursor) { cursor, _ ->
when (cursor.kind) {
CXCursorKind.CXCursor_TemplateRef -> {
ret = false
CXChildVisitResult.CXChildVisit_Break
}
else -> CXChildVisitResult.CXChildVisit_Recurse
}
}
return ret
}
private fun getFunctionParameters(cursor: CValue<CXCursor>): List<Parameter> {
val argNum = clang_Cursor_getNumArguments(cursor)
val args = (0..argNum - 1).map {
@@ -1024,6 +1210,13 @@ private fun indexDeclarations(nativeIndex: NativeIndexImpl): CompilationWithPCH
}
})
visitChildren(clang_getTranslationUnitCursor(translationUnit)) { cursor, _ ->
if (getContainingFile(cursor) in headers) {
nativeIndex.indexDeclaration(cursor)
}
CXChildVisitResult.CXChildVisit_Recurse
}
visitChildren(clang_getTranslationUnitCursor(translationUnit)) { cursor, _ ->
val file = getContainingFile(cursor)
if (file in headers && nativeIndex.library.includesDeclaration(cursor)) {
@@ -171,7 +171,7 @@ private fun reparseWithCodeSnippets(library: CompilationWithPCH,
names.forEach { name ->
val codeSnippetLines = when (library.language) {
Language.C, Language.OBJECTIVE_C ->
Language.C, Language.CPP, Language.OBJECTIVE_C ->
listOf("void $CODE_SNIPPET_FUNCTION_NAME_PREFIX$name() {",
" __auto_type KNI_INDEXER_VARIABLE_$name = $name;",
"}")
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.native.interop.indexer
enum class Language(val sourceFileExtension: String) {
C("c"),
CPP("cpp"),
OBJECTIVE_C("m")
}
@@ -156,10 +157,12 @@ abstract class StructDecl(val spelling: String) : TypeDeclaration {
abstract class StructDef(val size: Long, val align: Int, val decl: StructDecl) {
enum class Kind {
STRUCT, UNION
STRUCT, UNION, CLASS
}
abstract val methods: List<FunctionDecl>
abstract val members: List<StructMember>
abstract val staticFields: List<GlobalDecl>
abstract val kind: Kind
val fields: List<Field> get() = members.filterIsInstance<Field>()
@@ -224,11 +227,53 @@ abstract class ObjCCategory(val name: String, val clazz: ObjCClass) : ObjCContai
*/
data class Parameter(val name: String?, val type: Type, val nsConsumed: Boolean)
enum class CxxMethodKind {
None, // not supported yet?
Constructor,
Destructor,
StaticMethod,
InstanceMethod // virtual or non-virtual instance member method (non-static)
// do we need operators here?
// do we need to distinguish virtual and non-virtual? Static? Final?
}
/**
* C++ class method, constructor or destructor details
*/
class CxxMethodInfo(val receiverType: PointerType, val kind: CxxMethodKind = CxxMethodKind.InstanceMethod)
fun CxxMethodInfo.isConst() : Boolean = receiverType.pointeeIsConst
/**
* C function declaration.
*/
class FunctionDecl(val name: String, val parameters: List<Parameter>, val returnType: Type, val binaryName: String,
val isDefined: Boolean, val isVararg: Boolean)
val isDefined: Boolean, val isVararg: Boolean,
val parentName: String? = null, val cxxMethod: CxxMethodInfo? = null) {
val fullName: String = parentName?.let { "$parentName::$name" } ?: name
// C++ virtual or non-virtual instance member, i.e. has "this" receiver
val isCxxInstanceMethod: Boolean = cxxMethod != null && cxxMethod.kind == CxxMethodKind.InstanceMethod
/**
* C++ class or instance member function, i.e. any function in the scope of class/struct: method, static, ctor, dtor, cast operator, etc
*/
val isCxxMethod: Boolean = cxxMethod != null
&& this.cxxMethod.kind != CxxMethodKind.None
val isCxxConstructor: Boolean = cxxMethod != null && this.cxxMethod.kind == CxxMethodKind.Constructor
val isCxxDestructor: Boolean = cxxMethod != null && this.cxxMethod.kind == CxxMethodKind.Destructor
val cxxReceiverType: PointerType? = cxxMethod?.receiverType
val cxxReceiverClass: StructDecl? = cxxMethod?. let { (this.cxxMethod.receiverType.pointeeType as RecordType).decl }
}
class Namespace(val name: String, val parent: String? = null) {
val fullName: String = parent?.let { "$parent::$name" } ?: name
}
/**
* C typedef definition.
@@ -248,7 +293,9 @@ class StringConstantDef(name: String, type: Type, val value: String) : ConstantD
class WrappedMacroDef(name: String, val type: Type) : MacroDef(name)
class GlobalDecl(val name: String, val type: Type, val isConst: Boolean)
class GlobalDecl(val name: String, val type: Type, val isConst: Boolean, val parentName: String? = null) {
val fullName: String = parentName?.let { "$it::$name" } ?: name
}
/**
* C type.
@@ -279,7 +326,9 @@ data class RecordType(val decl: StructDecl) : Type
data class EnumType(val def: EnumDef) : Type
data class PointerType(val pointeeType: Type, val pointeeIsConst: Boolean = false) : Type
// when pointer type is provided by clang we'll use ots correct spelling
data class PointerType(val pointeeType: Type, val pointeeIsConst: Boolean = false,
val isLVReference: Boolean = false, val spelling: String? = null) : Type
// TODO: refactor type representation and support type modifiers more generally.
data class FunctionType(val parameterTypes: List<Type>, val returnType: Type) : Type
@@ -36,6 +36,46 @@ internal val CValue<CXType>.name: String get() = clang_getTypeSpelling(this).con
internal val CXTypeKind.spelling: String get() = clang_getTypeKindSpelling(this).convertAndDispose()
internal val CXCursorKind.spelling: String get() = clang_getCursorKindSpelling(this).convertAndDispose()
internal val CValue<CXCursor>.isPublic: Boolean get() {
val access = clang_getCXXAccessSpecifier(this)
return access != CX_CXXAccessSpecifier.CX_CXXProtected && access != CX_CXXAccessSpecifier.CX_CXXPrivate
}
/**
* TODO Accessibility needs better support
* Currently we provide binding (access) to static vars (= internal linkage)
* (i.e. following C policy, as the C header would be included into kotlin impl file
* Consistent approach to C++ would be:
* - Kotlin class inherits from C++ allowing overriding and protected access
* - namespace mapped to package
* - anon namespace members mapped to "internal" allowing access from the current translation unit
* To make this working we have to derive a complete C++ "proxy" class for each original one and declare C wrappers as friends
* BTW Such derived C++ proxy class is the only way to allow Kotlin to override the private virtual C++ methods (which is OK in C++)
* Without that C++ style callbacks via overriding would be limited or not supported
*/
internal fun CValue<CXCursor>.isRecursivelyPublic(): Boolean {
when {
clang_isDeclaration(kind) == 0 ->
return true // got the topmost declaration already
!isPublic ->
return false
kind == CXCursorKind.CXCursor_Namespace && getCursorSpelling(this).isEmpty() ->
return false
/*
* TODO FIXME In the current design we allow binding to static vars, but this won't work for anon namespaces and private members
* Need better (consistent( decision wrt accessibility.
*/
// clang_getCursorLinkage(this) == CXLinkageKind.CXLinkage_Internal ->
// return false; // check disabled for a while
else ->
return clang_getCursorSemanticParent(this).isRecursivelyPublic()
}
}
internal fun CValue<CXString>.convertAndDispose(): String {
try {
return clang_getCString(this)!!.toKString()