[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
@@ -129,7 +129,6 @@ sourceSets {
srcDirs("prebuilt/nativeInteropStubs/kotlin")
}
kotlin{
target {
}
@@ -137,7 +136,6 @@ sourceSets {
}
}
dependencies {
compile(project(":kotlin-stdlib"))
compile(project(":kotlin-native:Interop:Runtime"))
@@ -76,31 +76,7 @@ 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() {
public open class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean = false) : NativeIndex() {
private sealed class DeclarationID {
data class USR(val usr: String) : DeclarationID()
@@ -200,13 +176,17 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
return DeclarationID.USR(usr)
}
private fun getStructDeclAt(
protected fun getStructDeclAt(
cursor: CValue<CXCursor>
): StructDeclImpl = structRegistry.getOrPut(cursor, { createStructDecl(cursor) }) { decl ->
): StructDecl = structRegistry.getOrPut(cursor, { createStructDecl(cursor) }) { decl ->
val definitionCursor = clang_getCursorDefinition(cursor)
if (clang_Cursor_isNull(definitionCursor) == 0) {
assert(clang_isCursorDefinition(definitionCursor) != 0)
createStructDef(decl, cursor)
// TODO: is this a bug or this is a wrong thing to do?
// Otherwise c++ class definition is created from its forward declaration
// and hence is empty.
//createStructDef(decl, cursor)
createStructDef(decl, definitionCursor)
}
}
@@ -217,11 +197,12 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
return StructDeclImpl(typeSpelling, getLocation(cursor))
}
private fun visitClass(cursor: CValue<CXCursor>, clazz: StructDefImpl) {
private fun addCxxMembers(classCursor: CValue<CXCursor>, clazz: StructDefImpl) {
if (library.language != Language.CPP) return
// 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) {
visitChildren(classCursor) { cursor, _ ->
if (cursor.isCxxPublic) {
// 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 -> {
@@ -232,8 +213,7 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
getFunction(cursor, clazz.decl)?.let { clazz.methods.add(it) }
}
}
CXCursorKind.CXCursor_Constructor ->
getFunction(cursor, clazz.decl)?.let { clazz.methods.add(it) }
CXCursorKind.CXCursor_Constructor,
CXCursorKind.CXCursor_Destructor ->
getFunction(cursor, clazz.decl)?.let { clazz.methods.add(it) }
@@ -274,13 +254,15 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
)
structDef.members += fields
visitClass(cursor, structDef)
addCxxMembers(cursor, structDef)
structDecl.def = structDef
}
private fun addDeclaredFields(result: MutableList<StructMember>, structType: CValue<CXType>, containerType: CValue<CXType>) {
getFields(containerType).filter { it.isPublic }.forEach { fieldCursor ->
// TODO: We don't exactly preserve C++ layout here, but
// we don't allow general case C++ classes by value at the moment.
getFields(containerType).filter { library.language != Language.CPP || it.isCxxPublic }.forEach { fieldCursor ->
val name = getCursorSpelling(fieldCursor)
if (name.isNotEmpty()) {
val fieldType = convertCursorType(fieldCursor)
@@ -606,7 +588,7 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
else -> UnsupportedType
}
fun convertType(type: CValue<CXType>, typeAttributes: CValue<CXTypeAttributes>? = null): Type {
open fun convertType(type: CValue<CXType>, typeAttributes: CValue<CXTypeAttributes>? = null): Type {
val primitiveType = convertUnqualifiedPrimitiveType(type)
if (primitiveType != UnsupportedType) {
return primitiveType
@@ -615,7 +597,9 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
val kind = type.kind
return when (kind) {
CXType_Elaborated -> convertType(clang_Type_getNamedType(type))
CXType_Elaborated -> {
convertType(clang_Type_getNamedType(type))
}
CXType_Unexposed -> {
if (clang_getResultType(type).kind != CXTypeKind.CXType_Invalid) {
@@ -645,6 +629,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_LValueReference -> {
@@ -861,23 +846,11 @@ 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()) {
if (library.language == Language.CPP && !cursor.isRecursivelyCxxPublic()) {
// 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_CXXClass -> {
if (entityName == null) {
@@ -969,29 +942,25 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
}
}
fun indexDeclaration(cursor: CValue<CXCursor>): Unit {
fun indexCxxDeclaration(cursor: CValue<CXCursor>): Unit {
if (library.language != Language.CPP) return
if (!library.includesDeclaration(cursor)) {
return
}
if (cursor.isRecursivelyPublic()) {
if (cursor.isRecursivelyCxxPublic()) {
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)
}
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)
}
indexCxxFunction(cursor)
}
else -> {
@@ -1023,6 +992,32 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
getObjCProtocolAt(cursor)
}
}
protected open fun String.isUnknownTemplate() = false
private fun getParentName(cursor: CValue<CXCursor>, pkg: List<String> = emptyList()) : String? {
if (library.language != Language.CPP) return null
// 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)
}
private fun getFunction(cursor: CValue<CXCursor>, receiver: StructDecl? = null): FunctionDecl? {
if (!isFuncDeclEligible(cursor)) {
@@ -1030,10 +1025,16 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
return null
}
var name = clang_getCursorSpelling(cursor).convertAndDispose()
var returnType = convertType(clang_getCursorResultType(cursor), clang_getCursorResultTypeAttributes(cursor))
val cursorReturnType = clang_getCursorResultType(cursor)
val cursorReturnTypeSpelling = clang_getTypeSpelling(cursorReturnType).convertAndDispose()
if (cursorReturnTypeSpelling.isUnknownTemplate()) return null
var returnType = convertType(cursorReturnType, clang_getCursorResultTypeAttributes(cursor))
val parameters = mutableListOf<Parameter>()
parameters += getFunctionParameters(cursor)
parameters += getFunctionParameters(cursor) ?: return null
val binaryName = when (library.language) {
Language.C, Language.CPP, Language.OBJECTIVE_C -> clang_Cursor_getMangling(cursor).convertAndDispose()
@@ -1046,35 +1047,36 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
// 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
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
}
else -> CxxMethodKind.None // Not implemented. Not expected, OK to assert (?)
}
)
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)
@@ -1094,7 +1096,7 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
val encoding = clang_getDeclObjCTypeEncoding(cursor).convertAndDispose()
val returnType = convertType(clang_getCursorResultType(cursor), clang_getCursorResultTypeAttributes(cursor))
val parameters = getFunctionParameters(cursor)
val parameters = getFunctionParameters(cursor)!!
if (returnType == UnsupportedType || parameters.any { it.type == UnsupportedType }) {
return null // TODO: make a more universal fix.
@@ -1128,11 +1130,10 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
}
// Skip functions which parameter or return type is TemplateRef
private fun isFuncDeclEligible(cursor: CValue<CXCursor>): Boolean {
protected open fun isFuncDeclEligible(cursor: CValue<CXCursor>): Boolean {
var ret = true
visitChildren(cursor) { cursor, _ ->
when (cursor.kind) {
visitChildren(cursor) { childCursor, _ ->
when (childCursor.kind) {
CXCursorKind.CXCursor_TemplateRef -> {
ret = false
CXChildVisitResult.CXChildVisit_Break
@@ -1143,10 +1144,13 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
return ret
}
private fun getFunctionParameters(cursor: CValue<CXCursor>): List<Parameter> {
private fun getFunctionParameters(cursor: CValue<CXCursor>): List<Parameter>? {
val argNum = clang_Cursor_getNumArguments(cursor)
val args = (0..argNum - 1).map {
val argCursor = clang_Cursor_getArgument(cursor, it)
if (clang_getTypeSpelling(clang_getCursorType(argCursor)).convertAndDispose().isUnknownTemplate()) {
return null
}
val argName = getCursorSpelling(argCursor)
val type = convertCursorType(argCursor)
Parameter(argName, type,
@@ -1175,8 +1179,12 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
fun buildNativeIndexImpl(library: NativeLibrary, verbose: Boolean): IndexerResult {
val result = NativeIndexImpl(library, verbose)
val compilation = indexDeclarations(result)
return IndexerResult(result, compilation)
return buildNativeIndexImpl(result)
}
fun buildNativeIndexImpl(index: NativeIndexImpl): IndexerResult {
val compilation = indexDeclarations(index)
return IndexerResult(index, compilation)
}
private fun indexDeclarations(nativeIndex: NativeIndexImpl): CompilationWithPCH {
@@ -1210,11 +1218,13 @@ private fun indexDeclarations(nativeIndex: NativeIndexImpl): CompilationWithPCH
}
})
visitChildren(clang_getTranslationUnitCursor(translationUnit)) { cursor, _ ->
if (getContainingFile(cursor) in headers) {
nativeIndex.indexDeclaration(cursor)
if (nativeIndex.library.language == Language.CPP) {
visitChildren(clang_getTranslationUnitCursor(translationUnit)) { cursor, _ ->
if (getContainingFile(cursor) in headers) {
nativeIndex.indexCxxDeclaration(cursor)
}
CXChildVisitResult.CXChildVisit_Continue
}
CXChildVisitResult.CXChildVisit_Recurse
}
visitChildren(clang_getTranslationUnitCursor(translationUnit)) { cursor, _ ->
@@ -62,7 +62,6 @@ data class CompilationWithPCH(
override val compilerArgs: List<String>,
override val language: Language
) : Compilation {
constructor(compilerArgs: List<String>, precompiledHeader: String, language: Language)
: this(compilerArgs + listOf("-include-pch", precompiledHeader), language)
@@ -256,25 +255,20 @@ class FunctionDecl(val name: String, val parameters: List<Parameter>, val return
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
val isCxxInstanceMethod: Boolean get() = this.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 isCxxMethod: Boolean get() = this.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 }
val isCxxConstructor: Boolean get() = this.cxxMethod?.kind == CxxMethodKind.Constructor
val isCxxDestructor: Boolean get() = this.cxxMethod?.kind == CxxMethodKind.Destructor
val cxxReceiverType: PointerType? get() = cxxMethod?.receiverType
val cxxReceiverClass: StructDecl?
get() = 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.
*
@@ -294,7 +288,7 @@ 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, val parentName: String? = null) {
val fullName: String = parentName?.let { "$it::$name" } ?: name
val fullName: String get() = parentName?.let { "$it::$name" } ?: name
}
/**
@@ -324,6 +318,8 @@ object VoidType : Type
data class RecordType(val decl: StructDecl) : Type
data class ManagedType(val decl: StructDecl) : Type
data class EnumType(val def: EnumDef) : Type
// when pointer type is provided by clang we'll use ots correct spelling
@@ -26,17 +26,17 @@ import java.nio.file.Paths
import java.security.DigestInputStream
import java.security.MessageDigest
internal val CValue<CXType>.kind: CXTypeKind get() = this.useContents { kind }
val CValue<CXType>.kind: CXTypeKind get() = this.useContents { kind }
internal val CValue<CXCursor>.kind: CXCursorKind get() = this.useContents { kind }
val CValue<CXCursor>.kind: CXCursorKind get() = this.useContents { kind }
internal val CValue<CXCursor>.type: CValue<CXType> get() = clang_getCursorType(this)
internal val CValue<CXCursor>.spelling: String get() = clang_getCursorSpelling(this).convertAndDispose()
val CValue<CXCursor>.spelling: String get() = clang_getCursorSpelling(this).convertAndDispose()
internal val CValue<CXType>.name: String get() = clang_getTypeSpelling(this).convertAndDispose()
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() {
internal val CValue<CXCursor>.isCxxPublic: Boolean get() {
val access = clang_getCXXAccessSpecifier(this)
return access != CX_CXXAccessSpecifier.CX_CXXProtected && access != CX_CXXAccessSpecifier.CX_CXXPrivate
}
@@ -54,11 +54,11 @@ internal val CValue<CXCursor>.isPublic: Boolean get() {
* 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 {
internal fun CValue<CXCursor>.isRecursivelyCxxPublic(): Boolean {
when {
clang_isDeclaration(kind) == 0 ->
return true // got the topmost declaration already
!isPublic ->
!isCxxPublic ->
return false
kind == CXCursorKind.CXCursor_Namespace && getCursorSpelling(this).isEmpty() ->
return false
@@ -71,7 +71,7 @@ internal fun CValue<CXCursor>.isRecursivelyPublic(): Boolean {
// return false; // check disabled for a while
else ->
return clang_getCursorSemanticParent(this).isRecursivelyPublic()
return clang_getCursorSemanticParent(this).isRecursivelyCxxPublic()
}
}
@@ -194,7 +194,7 @@ internal fun CXTranslationUnit.ensureNoCompileErrors(): CXTranslationUnit {
internal typealias CursorVisitor = (cursor: CValue<CXCursor>, parent: CValue<CXCursor>) -> CXChildVisitResult
internal fun visitChildren(parent: CValue<CXCursor>, visitor: CursorVisitor) {
fun visitChildren(parent: CValue<CXCursor>, visitor: CursorVisitor) {
val visitorStableRef = StableRef.create(visitor)
try {
val clientData = visitorStableRef.asCPointer()
@@ -335,7 +335,11 @@ internal fun Compilation.withPrecompiledHeader(translationUnit: CXTranslationUni
val precompiledHeader = Files.createTempFile(null, ".pch").toFile().apply { this.deleteOnExit() }
clang_saveTranslationUnit(translationUnit, precompiledHeader.absolutePath, 0)
return CompilationWithPCH(this.compilerArgs, precompiledHeader.absolutePath, this.language)
return CompilationWithPCH(
this.compilerArgs,
precompiledHeader.absolutePath,
this.language
)
}
internal fun NativeLibrary.includesDeclaration(cursor: CValue<CXCursor>): Boolean {
@@ -23,6 +23,10 @@ annotation class CStruct(val spelling: String) {
@Retention(AnnotationRetention.BINARY)
annotation class VarType(val size: Long, val align: Int)
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
annotation class ManagedType
}
@Target(
@@ -59,6 +63,10 @@ public annotation class CCall(val id: String) {
@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.BINARY)
annotation class Consumed
@Target(AnnotationTarget.CONSTRUCTOR)
@Retention(AnnotationRetention.BINARY)
annotation class CppClassConstructor
}
/**
@@ -102,4 +110,4 @@ public annotation class CEnumEntryAlias(val entryName: String)
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
public annotation class CEnumVarTypeSize(val size: Int)
public annotation class CEnumVarTypeSize(val size: Int)
+56
View File
@@ -0,0 +1,56 @@
Skia interop support
====================
This is a plugin for cinterop that allows to interop with `Skia Graphics Library`.
Primarily targeted to be used by Skiko project.
Usage:
------
Add to the .def file the following clauses:
```
plugin = org.jetbrains.kotlin.native.cinterop.plugin.skia
language = C++
```
Implementation details
======================
Limited C++ interop provided via plain C wrappers and cinterop mechanism.
WARNING: this is by no means a general support for C++ by cinterop.
C++ features
------------
* C++ class:
+ Virtual and non-virtual methods
* Static methods mapped as companion ones
* Fields
* Static fields as companion ones
* Constructors are mapped to `__init__(args)` companion methods
* Destructor is mapped to `__destroy__(this)` companion method
* LValueReference parameters and return value internally handled as pointers
* Namespaces provided as simple mangled class name. It works but awfully ugly. Shall be fixed by mapping to packages.
* Nested C++ classes: same as namespaces, simple mangling. Shall be fixed.
* Access modifiers: only public members exposed to Kotlin. Anonymous namespaces are silently ignored.
Known issues
------------
* C++ object with nontrivial copy constructor may work incorrect when used as by value parameter or return typr.
"Nontrivial" in this context relates to objects which bahavior depends on memory location. Most of other non-POD objects may be handled well.
In fact, "by value" return type is mapped to CValue which is immutrable movable block. Particularly, non-const methods invoked with CValue receiver
via useContents mechanism run on temporary object and therefore does not modify the original copy. TBD.
Limitations
-----------
* Operators are not supported yet (silently ignored)
* LVReference is mapped to CPointer<T>? which is incorrect (should be notNull). This may cause segmentation fault in case of null would be sent as a parameter. TBD
* const overload not supported and cause compilation error. That is, two class methods with the same signature (`const` and `non-const`) can't be compiled. The same for function parameters: if two functions differ only in `const*` modifier of parameter, this will cause "conflicting overloads" error. TBD.
* C++ lambda type is not supported yet.
* Member pointer, member reference, rvalue reference and some other types are not wupported.
* Inheritance is not implemented yet. C++-style callbacks (overriding virtual method in Kotlin) may be implemented via plain C bridge (this can be done by hand as a workaround). TBD.
+32
View File
@@ -0,0 +1,32 @@
/*
* Copyright 2010-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
buildscript {
apply from: "$rootDir/kotlin-native/gradle/kotlinGradlePlugin.gradle"
}
apply plugin: 'kotlin'
dependencies {
implementation project(":kotlin-native:Interop:Indexer")
implementation project(":kotlin-native:Interop:StubGenerator")
}
compileKotlin {
kotlinOptions {
allWarningsAsErrors=true
}
}
@@ -0,0 +1,81 @@
/*
* 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.skia
import clang.*
import kotlinx.cinterop.CValue
import org.jetbrains.kotlin.native.interop.indexer.*
fun buildSkiaNativeIndexImpl(library: NativeLibrary, verbose: Boolean): IndexerResult {
val result = SkiaNativeIndexImpl(library, verbose)
return buildNativeIndexImpl(result)
}
class SkiaNativeIndexImpl(library: NativeLibrary, verbose: Boolean) : NativeIndexImpl(library, verbose) {
override fun convertType(type: CValue<CXType>, typeAttributes: CValue<CXTypeAttributes>?): Type {
if (type.kind == CXTypeKind.CXType_Record) {
val decl: StructDecl = getStructDeclAt(clang_getTypeDeclaration(type))
if (decl.isSkiaSharedPointer) {
return ManagedType(decl)
}
}
return super.convertType(type, typeAttributes)
}
// Skip functions which parameter or return type is TemplateRef
override fun isFuncDeclEligible(cursor: CValue<CXCursor>): Boolean =
cursor.containsOnlySkiaSharedPointerTemplates()
override fun String.isUnknownTemplate() = // TODO: this is a hack.
this.isCppTemplate && !this.isSkiaSharedPointer
}
fun CValue<CXCursor>.containsTemplates(): Boolean {
var ret = false
visitChildren(this) { childCursor, _ ->
when (childCursor.kind) {
CXCursorKind.CXCursor_TemplateRef -> {
ret = true
CXChildVisitResult.CXChildVisit_Break
}
else -> CXChildVisitResult.CXChildVisit_Recurse
}
}
return ret
}
fun CValue<CXCursor>.containsOnlySkiaSharedPointerTemplates(): Boolean {
var ret = true
visitChildren(this) { childCursor, _ ->
when (childCursor.kind) {
CXCursorKind.CXCursor_TemplateRef ->
if (childCursor.spelling == "sk_sp" && !childCursor.containsTemplates()) {
CXChildVisitResult.CXChildVisit_Continue
} else {
ret = false
CXChildVisitResult.CXChildVisit_Break
}
else -> CXChildVisitResult.CXChildVisit_Recurse
}
}
return ret
}
val StructDecl.isSkiaSharedPointer: Boolean
get() = spelling.isSkiaSharedPointer
val StructDecl.stripSkiaSharedPointer: String
get() {
assert(this.isSkiaSharedPointer)
return this.spelling.drop(6).dropLast(1).let { // TODO: this is a hack.
if (it.startsWith("const ")) it.drop(6) else it
}
}
private val String.isCppTemplate: Boolean // TODO: this is a hack.
get() = this.contains("<") && this.endsWith(">")
private val String.isSkiaSharedPointer: Boolean // TODO: this is a hack.
get() = this.startsWith("sk_sp<") && this.endsWith(">")
@@ -0,0 +1,32 @@
/*
* 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.skia
import org.jetbrains.kotlin.native.interop.gen.ManagedTypePassing
import org.jetbrains.kotlin.native.interop.gen.StubIrContext
import org.jetbrains.kotlin.native.interop.gen.getStringRepresentation
import org.jetbrains.kotlin.native.interop.gen.jvm.Plugin
import org.jetbrains.kotlin.native.interop.indexer.IndexerResult
import org.jetbrains.kotlin.native.interop.indexer.ManagedType
import org.jetbrains.kotlin.native.interop.indexer.NativeLibrary
import org.jetbrains.kotlin.native.interop.indexer.Type
class SkiaPlugin : Plugin {
override val name = "Skia"
override fun buildNativeIndex(library: NativeLibrary, verbose: Boolean): IndexerResult =
buildSkiaNativeIndexImpl(library, verbose)
override val managedTypePassing = object : ManagedTypePassing() {
override val ManagedType.passValue: String get() = "sk_ref_sp<${this.decl.stripSkiaSharedPointer}>"
override val ManagedType.returnValue: String get() = ".release()"
}
override val ManagedType.stringRepresentation: String get() {
assert(this.decl.isSkiaSharedPointer)
return "${this.decl.stripSkiaSharedPointer}*"
}
override fun stubsBuildingContext(stubIrContext: StubIrContext) = SkiaStubsBuildingContextImpl(stubIrContext)
}
@@ -0,0 +1,26 @@
/*
* 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.skia
import org.jetbrains.kotlin.native.interop.gen.Classifier
import org.jetbrains.kotlin.native.interop.gen.StubIrContext
import org.jetbrains.kotlin.native.interop.gen.StubsBuildingContextImpl
import org.jetbrains.kotlin.native.interop.indexer.StructDecl
class SkiaStubsBuildingContextImpl(stubIrContext: StubIrContext) : StubsBuildingContextImpl(stubIrContext) {
override val declarationMapper = SkiaDeclarationMapperImpl()
inner class SkiaDeclarationMapperImpl : DeclarationMapperImpl() {
override fun getKotlinClassForManaged(structDecl: StructDecl): Classifier {
assert(structDecl.isSkiaSharedPointer)
val struct = structDecl.stripSkiaSharedPointer
val structArgument = nativeIndex.structs.singleOrNull {
it.spelling == struct && it.def != null
} ?: error("Expected to find a single template arg struct by name: ${struct}")
return getKotlinClassForPointed(structArgument)
}
}
}
@@ -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")