[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)
@@ -1,12 +1,25 @@
Current status
==============
Skia interop support
====================
Limited C++ interop provided via plain C wrappers and cinterop mechanism.
This is a plugin for cinterop that allows to interop with `Skia Graphics Library`.
Primarily targeted to be used by Skiko project.
Usage:
------
Add `Language = C++` to .def file or `compilerOpts = -x c++` to command line or def file.
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
------------
+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")
@@ -6,48 +6,66 @@ import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.konan.exec.Command
import org.jetbrains.kotlin.konan.file.*
import org.jetbrains.kotlin.konan.target.ClangArgs
import org.jetbrains.kotlin.konan.target.Family
import org.jetbrains.kotlin.konan.target.KonanTarget
private const val dumpBridges = false
class CStubsManager(private val target: KonanTarget) {
fun getUniqueName(prefix: String) = "$prefix${counter++}"
fun addStub(kotlinLocation: CompilerMessageLocation?, lines: List<String>) {
fun addStub(kotlinLocation: CompilerMessageLocation?, lines: List<String>, language: String) {
val stubs = languageToStubs.getOrPut(language) { mutableListOf() }
stubs += Stub(kotlinLocation, lines)
}
fun compile(clang: ClangArgs, messageCollector: MessageCollector, verbose: Boolean): File? {
if (stubs.isEmpty()) return null
fun compile(clang: ClangArgs, messageCollector: MessageCollector, verbose: Boolean): List<File> {
if (languageToStubs.isEmpty()) return emptyList()
val compilerOptions = mutableListOf<String>()
val sourceFileExtension = when {
target.family.isAppleFamily -> {
compilerOptions += "-fobjc-arc"
".m" // TODO: consider managing C and Objective-C stubs separately.
val bitcodes = languageToStubs.entries.map { (language, stubs) ->
val compilerOptions = mutableListOf<String>()
val sourceFileExtension = when {
language == "C++" -> ".cpp"
target.family.isAppleFamily -> {
compilerOptions += "-fobjc-arc"
".m" // TODO: consider managing C and Objective-C stubs separately.
}
else -> ".c"
}
else -> ".c"
}
val cSource = createTempFile("cstubs", sourceFileExtension).deleteOnExit()
cSource.writeLines(stubs.flatMap { it.lines })
val cSource = createTempFile("cstubs", sourceFileExtension).deleteOnExit()
cSource.writeLines(stubs.flatMap { it.lines })
val bitcode = createTempFile("cstubs", ".bc").deleteOnExit()
val bitcode = createTempFile("cstubs", ".bc").deleteOnExit()
val cSourcePath = cSource.absolutePath
val cSourcePath = cSource.absolutePath
val clangCommand = clang.clangC(*compilerOptions.toTypedArray(), "-O2",
cSourcePath, "-emit-llvm", "-c", "-o", bitcode.absolutePath)
val clangCommand = clang.clangC(
*compilerOptions.toTypedArray(), "-O2",
cSourcePath, "-emit-llvm", "-c", "-o", bitcode.absolutePath
)
if (dumpBridges) {
println("CSTUBS for ${language}")
stubs.flatMap { it.lines }.forEach {
println(it)
}
println("CSTUBS in ${cSource.absolutePath}")
println("CSTUBS CLANG COMMAND:")
println(clangCommand.joinToString(" "))
}
val result = Command(clangCommand).getResult(withErrors = true)
if (result.exitCode != 0) {
reportCompilationErrors(cSourcePath, result, messageCollector, verbose)
val result = Command(clangCommand).getResult(withErrors = true)
if (result.exitCode != 0) {
reportCompilationErrors(cSourcePath, stubs, result, messageCollector, verbose)
}
bitcode
}
return bitcode
return bitcodes
}
private fun reportCompilationErrors(
cSourcePath: String,
stubs: List<Stub>,
result: Command.Result,
messageCollector: MessageCollector,
verbose: Boolean
@@ -88,7 +106,7 @@ class CStubsManager(private val target: KonanTarget) {
throw KonanCompilationException()
}
private val stubs = mutableListOf<Stub>()
private val languageToStubs = mutableMapOf<String, MutableList<Stub>>()
private class Stub(val kotlinLocation: CompilerMessageLocation?, val lines: List<String>)
private var counter = 0
}
@@ -8,16 +8,13 @@ import llvm.*
import org.jetbrains.kotlin.backend.common.serialization.KlibIrVersion
import org.jetbrains.kotlin.backend.common.serialization.metadata.KlibMetadataVersion
import org.jetbrains.kotlin.backend.konan.llvm.*
import org.jetbrains.kotlin.backend.konan.llvm.Llvm
import org.jetbrains.kotlin.backend.konan.llvm.objc.linkObjC
import org.jetbrains.kotlin.konan.CURRENT
import org.jetbrains.kotlin.library.KotlinAbiVersion
import org.jetbrains.kotlin.konan.CompilerVersion
import org.jetbrains.kotlin.konan.file.isBitcode
import org.jetbrains.kotlin.library.*
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.konan.target.Family
import org.jetbrains.kotlin.konan.library.impl.buildLibrary
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.library.*
/**
* Supposed to be true for a single LLVM module within final binary.
@@ -48,7 +45,11 @@ val CompilerOutputKind.isCache: Boolean
internal fun produceCStubs(context: Context) {
val llvmModule = context.llvmModule!!
context.cStubsManager.compile(context.config.clang, context.messageCollector, context.inVerbosePhase)?.let {
context.cStubsManager.compile(
context.config.clang,
context.messageCollector,
context.inVerbosePhase
).forEach {
parseAndLinkBitcodeFile(llvmModule, it.absolutePath)
}
}
@@ -116,7 +116,18 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) {
val exportObjCClass = packageScope.getContributedClass("ExportObjCClass")
val CreateNSStringFromKString = packageScope.getContributedFunctions("CreateNSStringFromKString").single()
val nativeHeap = packageScope.getContributedClass("nativeHeap")
val cPointed = packageScope.getContributedClass("CPointed")
val interopGetPtr = packageScope.getContributedVariables("ptr").single {
val singleTypeParameter = it.typeParameters.singleOrNull()
val singleTypeParameterUpperBound = singleTypeParameter?.upperBounds?.singleOrNull()
val extensionReceiverParameter = it.extensionReceiverParameter
singleTypeParameterUpperBound != null &&
extensionReceiverParameter != null &&
TypeUtils.getClassDescriptor(singleTypeParameterUpperBound) == cPointed &&
extensionReceiverParameter.type == singleTypeParameter.defaultType
}.getter!!
}
private fun MemberScope.getContributedVariables(name: String) =
@@ -13,6 +13,8 @@ object RuntimeNames {
val cStructMemberAt = FqName("kotlinx.cinterop.internal.CStruct.MemberAt")
val cStructArrayMemberAt = FqName("kotlinx.cinterop.internal.CStruct.ArrayMemberAt")
val cStructBitField = FqName("kotlinx.cinterop.internal.CStruct.BitField")
val cStruct = FqName("kotlinx.cinterop.internal.CStruct")
val managedType = FqName("kotlinx.cinterop.internal.CStruct.ManagedType")
val objCMethodAnnotation = FqName("kotlinx.cinterop.ObjCMethod")
val objCMethodImp = FqName("kotlinx.cinterop.ObjCMethodImp")
val independent = FqName("kotlin.native.internal.Independent")
@@ -8,12 +8,9 @@ import org.jetbrains.kotlin.backend.common.lower.at
import org.jetbrains.kotlin.backend.common.lower.irNot
import org.jetbrains.kotlin.backend.konan.PrimitiveBinaryType
import org.jetbrains.kotlin.backend.konan.RuntimeNames
import org.jetbrains.kotlin.backend.konan.ir.konanLibrary
import org.jetbrains.kotlin.backend.konan.getObjCMethodInfo
import org.jetbrains.kotlin.backend.konan.ir.*
import org.jetbrains.kotlin.backend.konan.ir.KonanSymbols
import org.jetbrains.kotlin.backend.konan.ir.buildSimpleAnnotation
import org.jetbrains.kotlin.backend.konan.ir.getAnnotationArgumentValue
import org.jetbrains.kotlin.backend.konan.ir.typeWithStarProjections
import org.jetbrains.kotlin.backend.konan.isObjCMetaClass
import org.jetbrains.kotlin.backend.konan.lower.FunctionReferenceLowering
import org.jetbrains.kotlin.descriptors.ClassKind
@@ -50,6 +47,7 @@ internal interface KotlinStubs {
val irBuiltIns: IrBuiltIns
val symbols: KonanSymbols
val target: KonanTarget
val language: String
fun addKotlin(declaration: IrDeclaration)
fun addC(lines: List<String>)
fun getUniqueCName(prefix: String): String
@@ -111,10 +109,10 @@ private fun KotlinToCCallBuilder.buildKotlinBridgeCall(transformCall: (IrMemberA
transformCall
)
private fun IrType.isManagedType(): Boolean= this.classOrNull?.owner?.hasAnnotation(RuntimeNames.managedType) ?: false
internal fun KotlinStubs.generateCCall(expression: IrCall, builder: IrBuilderWithScope, isInvoke: Boolean,
foreignExceptionMode: ForeignExceptionMode.Mode = ForeignExceptionMode.default): IrExpression {
require(expression.dispatchReceiver == null) { renderCompilerError(expression) }
val callBuilder = KotlinToCCallBuilder(builder, this, isObjCMethod = false, foreignExceptionMode)
val callee = expression.symbol.owner
@@ -125,6 +123,7 @@ internal fun KotlinStubs.generateCCall(expression: IrCall, builder: IrBuilderWit
val targetFunctionName: String
if (isInvoke) {
require(expression.dispatchReceiver == null) { renderCompilerError(expression) }
targetPtrParameter = callBuilder.passThroughBridge(
expression.extensionReceiver!!,
symbols.interopCPointer.typeWithStarProjections,
@@ -148,7 +147,14 @@ internal fun KotlinStubs.generateCCall(expression: IrCall, builder: IrBuilderWit
val arguments = (0 until expression.valueArgumentsCount).map {
expression.getValueArgument(it)
}
callBuilder.addArguments(arguments, callee)
val receiverParameter = expression.symbol.owner.dispatchReceiverParameter
val self: List<IrExpression> = when {
receiverParameter == null -> emptyList()
receiverParameter.type.classOrNull?.owner?.isCompanion == true -> emptyList()
else -> listOf(expression.dispatchReceiver!!)
}
callBuilder.addArguments(self + arguments, callee)
}
val returnValuePassing = if (isInvoke) {
@@ -176,7 +182,13 @@ internal fun KotlinStubs.generateCCall(expression: IrCall, builder: IrBuilderWit
private fun KotlinToCCallBuilder.addArguments(arguments: List<IrExpression?>, callee: IrFunction) {
arguments.forEachIndexed { index, argument ->
val parameter = callee.valueParameters[index]
val parameter = if (callee.dispatchReceiverParameter != null &&
(callee.dispatchReceiverParameter?.type?.isManagedType() == true)) {
if (index == 0) callee.dispatchReceiverParameter!! else callee.valueParameters[index-1]
} else {
callee.valueParameters[index]
}
if (parameter.isVararg) {
require(index == arguments.lastIndex) { stubs.renderCompilerError(argument) }
addVariadicArguments(argument)
@@ -472,7 +484,7 @@ private fun CCallbackBuilder.buildCFunction(): String {
val cLines = mutableListOf<String>()
cLines += "${cFunctionBuilder.buildSignature(result)} {"
cLines += "${cFunctionBuilder.buildSignature(result, stubs.language)} {"
cLines += cBodyLines
cLines += "}"
@@ -724,12 +736,18 @@ private fun KotlinStubs.mapType(
val cStructType = getNamedCStructType(kotlinClass)
require(cStructType != null) { renderCompilerError(location) }
StructValuePassing(kotlinClass, cStructType)
if (type.isManagedType()) {
// TODO: this should probably be better abstracted in a plugin.
// For Skia plugin we release sk_sp on the C++ side passing just the raw pointer.
// So managed by value is handled as voidPtr here for now.
TrivialValuePassing(type, CTypes.voidPtr)
} else {
StructValuePassing(kotlinClass, cStructType)
}
}
type.classOrNull?.isSubtypeOfClass(symbols.nativePointed) == true -> {
type.classOrNull?.isSubtypeOfClass(symbols.nativePointed) == true ->
TrivialValuePassing(type, CTypes.voidPtr)
}
type.isFunction() -> {
require(!variadic) { renderCompilerError(location) }
@@ -738,7 +756,7 @@ private fun KotlinStubs.mapType(
type.isObjCReferenceType(target, irBuiltIns) -> ObjCReferenceValuePassing(symbols, type, retained = retained)
else -> throwCompilerError(location, "doesn't correspond to any C type")
else -> throwCompilerError(location, "doesn't correspond to any C type: ${type.render()}")
}
private class CExpression(val expression: String, val type: CType)
@@ -883,6 +901,7 @@ private class StructValuePassing(private val kotlinClass: IrClass, override val
bridgeCallBuilder.prepare += kotlinPointed
val cPointer = passThroughBridge(irGet(kotlinPointed), kotlinPointedType, CTypes.pointer(cType))
cBridgeBodyLines += "*${cPointer.name} = $expression;"
buildKotlinBridgeCall {
@@ -1258,7 +1277,7 @@ private class ObjCBlockPointerValuePassing(
val block = buildString {
append('^')
append(callbackBuilder.cFunctionBuilder.buildSignature(""))
append(callbackBuilder.cFunctionBuilder.buildSignature("", stubs.language))
append(" { ")
callbackBuilder.cBodyLines.forEach {
append(it)
@@ -22,7 +22,7 @@ import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl
import org.jetbrains.kotlin.ir.symbols.impl.IrValueParameterSymbolImpl
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.impl.IrUninitializedType
import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.util.irBuilder
import org.jetbrains.kotlin.ir.util.irCatch
import org.jetbrains.kotlin.konan.ForeignExceptionMode
@@ -51,17 +51,19 @@ internal class CFunctionBuilder {
fun getType(): CType = CTypes.function(returnType, parameters.map { it.type }, variadic)
fun buildSignature(name: String): String = returnType.render(buildString {
append(name)
append('(')
parameters.joinTo(this)
if (parameters.isEmpty()) {
if (!variadic) append("void")
} else {
if (variadic) append(", ...")
}
append(')')
})
fun buildSignature(name: String, language: String): String =
(if (language == "C++") "extern \"C\" const " else "") +
returnType.render(buildString {
append(name)
append('(')
parameters.joinTo(this)
if (parameters.isEmpty()) {
if (!variadic) append("void")
} else {
if (variadic) append(", ...")
}
append(')')
})
}
@@ -145,7 +147,7 @@ internal class KotlinCBridgeBuilder(
startOffset: Int,
endOffset: Int,
cName: String,
stubs: KotlinStubs,
val stubs: KotlinStubs,
isKotlinToC: Boolean,
foreignExceptionMode: ForeignExceptionMode.Mode = ForeignExceptionMode.default
) {
@@ -163,7 +165,7 @@ internal class KotlinCBridgeBuilder(
cBridgeBuilder.setReturnType(cReturnType)
}
fun buildCSignature(name: String): String = cBridgeBuilder.buildSignature(name)
fun buildCSignature(name: String): String = cBridgeBuilder.buildSignature(name, stubs.language)
fun buildKotlinBridge() = kotlinBridgeBuilder.build()
}
@@ -33,7 +33,7 @@ internal fun IrType.isCEnumType(): Boolean {
private val cCall = RuntimeNames.cCall
// Make sure external stubs always get proper annotaions.
private fun IrDeclaration.hasCCallAnnotation(name: String): Boolean =
fun IrDeclaration.hasCCallAnnotation(name: String): Boolean =
this.annotations.hasAnnotation(cCall.child(Name.identifier(name)))
// LazyIr doesn't pass annotations from descriptor to IrValueParameter.
|| this.descriptor.annotations.hasAnnotation(cCall.child(Name.identifier(name)))
@@ -63,6 +63,7 @@ internal class KonanSymbols(
val nativePointed = symbolTable.referenceClass(context.interopBuiltIns.nativePointed)
val nativePtrType = nativePtr.typeWith(arguments = emptyList())
val nonNullNativePtr = symbolTable.referenceClass(context.nonNullNativePtr)
val nonNullNativePtrType = nonNullNativePtr.typeWith(arguments = emptyList())
val immutableBlobOf = symbolTable.referenceSimpleFunction(context.immutableBlobOf)
@@ -205,6 +206,10 @@ internal class KonanSymbols(
val nativeMemUtils = symbolTable.referenceClass(context.interopBuiltIns.nativeMemUtils)
val nativeHeap = symbolTable.referenceClass(context.interopBuiltIns.nativeHeap)
val interopGetPtr = symbolTable.referenceSimpleFunction(context.interopBuiltIns.interopGetPtr)
val readBits = interopFunction("readBits")
val writeBits = interopFunction("writeBits")
@@ -148,7 +148,8 @@ internal interface DescriptorToIrTranslationMixin {
private fun IrDeclaration.generateAnnotations() {
annotations += descriptor.annotations.map {
typeTranslator.constantValueGenerator.generateAnnotationConstructorCall(it)!!
typeTranslator.constantValueGenerator.generateAnnotationConstructorCall(it)
?: error("Could not generate annotations for $it")
}
}
}
@@ -54,7 +54,7 @@ internal class IrProviderForCEnumAndCStructStubs(
private val cStructCompanionGenerator =
CStructVarCompanionGenerator(context, interopBuiltIns)
private val cStructClassGenerator =
CStructVarClassGenerator(context, interopBuiltIns, cStructCompanionGenerator)
CStructVarClassGenerator(context, interopBuiltIns, cStructCompanionGenerator, symbols)
fun isCEnumOrCStruct(declarationDescriptor: DeclarationDescriptor): Boolean =
declarationDescriptor.run { findCEnumDescriptor(interopBuiltIns) ?: findCStructDescriptor(interopBuiltIns) } != null
@@ -92,6 +92,10 @@ internal class IrProviderForCEnumAndCStructStubs(
fun getDeclaration(descriptor: DeclarationDescriptor, idSignature: IdSignature, file: IrFile, symbolKind: BinarySymbolData.SymbolKind): IrSymbolOwner {
return symbolTable.run {
when (symbolKind) {
BinarySymbolData.SymbolKind.CONSTRUCTOR_SYMBOL -> declareConstructorFromLinker(descriptor as ClassConstructorDescriptor, idSignature) { s ->
generateIrIfNeeded(s, file)
s.owner
}
BinarySymbolData.SymbolKind.CLASS_SYMBOL -> declareClassFromLinker(descriptor as ClassDescriptor, idSignature) { s ->
generateIrIfNeeded(s, file)
s.owner
@@ -5,17 +5,15 @@
package org.jetbrains.kotlin.backend.konan.ir.interop.cstruct
import org.jetbrains.kotlin.backend.konan.InteropBuiltIns
import org.jetbrains.kotlin.backend.konan.ir.KonanSymbols
import org.jetbrains.kotlin.backend.konan.ir.interop.DescriptorToIrTranslationMixin
import org.jetbrains.kotlin.backend.konan.ir.interop.irInstanceInitializer
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.ir.builders.irBlockBody
import org.jetbrains.kotlin.ir.builders.irCall
import org.jetbrains.kotlin.ir.builders.irGet
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrConstructor
import org.jetbrains.kotlin.ir.declarations.IrDeclarationContainer
import org.jetbrains.kotlin.ir.declarations.addMember
import org.jetbrains.kotlin.ir.builders.irGetObject
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
import org.jetbrains.kotlin.ir.expressions.impl.IrDelegatingConstructorCallImpl
import org.jetbrains.kotlin.ir.util.*
@@ -24,7 +22,8 @@ import org.jetbrains.kotlin.psi2ir.generators.GeneratorContext
internal class CStructVarClassGenerator(
context: GeneratorContext,
private val interopBuiltIns: InteropBuiltIns,
private val companionGenerator: CStructVarCompanionGenerator
private val companionGenerator: CStructVarCompanionGenerator,
private val symbols: KonanSymbols
) : DescriptorToIrTranslationMixin {
override val irBuiltIns: IrBuiltIns = context.irBuiltIns
@@ -48,12 +47,25 @@ internal class CStructVarClassGenerator(
createClass(descriptor) { irClass ->
irClass.addMember(createPrimaryConstructor(irClass))
irClass.addMember(companionGenerator.generate(descriptor))
descriptor.constructors
.filterNot { it.isPrimary }
.map {
val constructor = createSecondaryConstructor(it)
irClass.addMember(constructor)
}
descriptor.unsubstitutedMemberScope
.getContributedDescriptors()
.filterIsInstance<PropertyDescriptor>()
.filter { it.kind != CallableMemberDescriptor.Kind.FAKE_OVERRIDE }
.map(this::createProperty)
.forEach(irClass::addMember)
.getContributedDescriptors()
.filterIsInstance<CallableMemberDescriptor>()
.filterNot { it.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE }
.map {
when (it) {
is PropertyDescriptor -> createProperty(it)
is SimpleFunctionDescriptor -> createFunction(it)
else -> null
}
}
.filterNotNull()
.forEach(irClass::addMember)
}
private fun createPrimaryConstructor(irClass: IrClass): IrConstructor {
@@ -74,4 +86,14 @@ internal class CStructVarClassGenerator(
}
}
}
private fun createSecondaryConstructor(descriptor: ClassConstructorDescriptor): IrConstructor {
return createConstructor(descriptor).also {
postLinkageSteps.add {
it.body = irBuilder(irBuiltIns, it.symbol, SYNTHETIC_OFFSET, SYNTHETIC_OFFSET).irBlockBody {
// Empty. The real body is constructed at the call site by the interop lowering phase.
}
}
}
}
}
@@ -8,12 +8,16 @@ import org.jetbrains.kotlin.backend.konan.InteropBuiltIns
import org.jetbrains.kotlin.backend.konan.descriptors.getArgumentValueOrNull
import org.jetbrains.kotlin.backend.konan.ir.interop.DescriptorToIrTranslationMixin
import org.jetbrains.kotlin.backend.konan.ir.interop.irInstanceInitializer
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
import org.jetbrains.kotlin.ir.builders.irBlockBody
import org.jetbrains.kotlin.ir.builders.irInt
import org.jetbrains.kotlin.ir.builders.irLong
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrConstructor
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.addMember
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
import org.jetbrains.kotlin.ir.expressions.impl.IrDelegatingConstructorCallImpl
@@ -41,6 +45,19 @@ internal class CStructVarCompanionGenerator(
val size = annotation.getArgumentValueOrNull<Long>("size")!!
val align = annotation.getArgumentValueOrNull<Int>("align")!!
companionIrClass.addMember(createCompanionConstructor(companionIrClass.descriptor, size, align))
companionIrClass.descriptor.unsubstitutedMemberScope
.getContributedDescriptors()
.filterIsInstance<CallableMemberDescriptor>()
.filterNot { it.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE }
.mapNotNull {
when (it) {
is PropertyDescriptor -> createProperty(it)
is SimpleFunctionDescriptor -> createFunction(it)
else -> null
}
}
.forEach(companionIrClass::addMember)
}
private fun createCompanionConstructor(companionObjectDescriptor: ClassDescriptor, size: Long, align: Int): IrConstructor {
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.backend.common.atMostOne
import org.jetbrains.kotlin.backend.common.ir.copyTo
import org.jetbrains.kotlin.backend.common.lower.*
import org.jetbrains.kotlin.backend.konan.*
import org.jetbrains.kotlin.backend.konan.cgen.hasCCallAnnotation
import org.jetbrains.kotlin.backend.konan.ir.*
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
@@ -297,6 +298,10 @@ private class InlineClassTransformer(private val context: Context) : IrBuildingT
if (declaration.constructedClass.isNativePrimitiveType()) {
// Constructors for these types aren't used and actually are malformed (e.g. lack the parameter).
// Skipping here for simplicity.
} else if (declaration.hasCCallAnnotation("CppClassConstructor") && !declaration.isPrimary) {
// At this point secondary cpp constructor calls have already been transformed
// by interop lowering. So don't mess with them.
// Otherwise we could assert having assumptions on (empty at the moment) body of the constructor.
} else {
buildLoweredConstructor(declaration)
}
@@ -6,10 +6,7 @@
package org.jetbrains.kotlin.backend.konan.lower
import org.jetbrains.kotlin.backend.common.*
import org.jetbrains.kotlin.backend.common.ir.allParameters
import org.jetbrains.kotlin.backend.common.ir.copyTo
import org.jetbrains.kotlin.backend.common.ir.createDispatchReceiverParameter
import org.jetbrains.kotlin.backend.common.ir.simpleFunctions
import org.jetbrains.kotlin.backend.common.ir.*
import org.jetbrains.kotlin.backend.common.lower.*
import org.jetbrains.kotlin.backend.konan.*
import org.jetbrains.kotlin.backend.konan.cgen.*
@@ -17,6 +14,7 @@ import org.jetbrains.kotlin.backend.konan.descriptors.allOverriddenFunctions
import org.jetbrains.kotlin.backend.konan.descriptors.synthesizedName
import org.jetbrains.kotlin.backend.konan.ir.*
import org.jetbrains.kotlin.backend.konan.ir.companionObject
import org.jetbrains.kotlin.backend.konan.ir.isFinalClass
import org.jetbrains.kotlin.backend.konan.llvm.IntrinsicType
import org.jetbrains.kotlin.backend.konan.llvm.tryGetIntrinsicType
import org.jetbrains.kotlin.backend.konan.serialization.resolveFakeOverrideMaybeAbstract
@@ -45,6 +43,7 @@ import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.konan.ForeignExceptionMode
import org.jetbrains.kotlin.konan.library.KonanLibrary
internal class InteropLowering(context: Context) : FileLoweringPass {
// TODO: merge these lowerings.
@@ -83,12 +82,19 @@ private abstract class BaseInteropIrTransformer(private val context: Context) :
override val irBuiltIns get() = context.irBuiltIns
override val symbols get() = context.ir.symbols
val klib: KonanLibrary? get() {
return (element as? IrCall)?.symbol?.owner?.konanLibrary as? KonanLibrary
}
override val language: String
get() = klib?.manifestProperties?.getProperty("language") ?: "C"
override fun addKotlin(declaration: IrDeclaration) {
addTopLevel(declaration)
}
override fun addC(lines: List<String>) {
context.cStubsManager.addStub(location, lines)
context.cStubsManager.addStub(location, lines, language)
}
override fun getUniqueCName(prefix: String) =
@@ -763,6 +769,10 @@ private class InteropTransformer(val context: Context, override val irFile: IrFi
override fun visitConstructorCall(expression: IrConstructorCall): IrExpression {
expression.transformChildrenVoid(this)
if (expression.symbol.owner.hasCCallAnnotation("CppClassConstructor")) {
return transformSecondaryCppConstructorCall(expression)
}
val callee = expression.symbol.owner
val inlinedClass = callee.returnType.getInlinedClassNative()
require(inlinedClass?.descriptor != interop.cPointer) { renderCompilerError(expression) }
@@ -793,6 +803,58 @@ private class InteropTransformer(val context: Context, override val irFile: IrFi
}
}
private fun transformSecondaryCppConstructorCall(expression: IrConstructorCall): IrExpression {
val irConstructor = expression.symbol.owner
val irClass = irConstructor.constructedClass
val primaryConstructor = irClass.primaryConstructor!!.symbol
// TODO: don't use it is deprecated.
val alloc = symbols.interopAllocType
val nativeHeap = symbols.nativeHeap
val interopGetPtr = symbols.interopGetPtr
val correspondingInit = irClass.companionObject()!!
.declarations
.filterIsInstance<IrSimpleFunction>()
.filter { it.name.toString() == "__init__"}
.filter { it.valueParameters.size == irConstructor.valueParameters.size + 1}
.single {
it.valueParameters.drop(1).mapIndexed() { index, initParameter ->
initParameter.type == irConstructor.valueParameters[index].type
}.all{ it }
}
val irBlock = builder.at(expression)
.irBlock {
val call = irCall(primaryConstructor).also {
val nativePointed = irCall(alloc).apply {
extensionReceiver = irGetObject(nativeHeap)
putValueArgument(0, irGetObject(irClass.companionObject()!!.symbol))
}
val nativePtr = irCall(symbols.interopNativePointedGetRawPointer).apply {
extensionReceiver = nativePointed
}
it.putValueArgument(0, nativePtr)
}
val tmp = irTemporary(call)
val initCall = irCall(correspondingInit.symbol).apply {
putValueArgument(0,
irCall(interopGetPtr).apply {
extensionReceiver = irGet(tmp)
}
)
for (index in 0 until expression.valueArgumentsCount) {
putValueArgument(index+1, expression.getValueArgument(index)!!)
}
}
val initCCall = generateCCall(initCall)
+initCCall
+irGet(tmp)
}
return irBlock
}
/**
* Handle `const val`s that come from interop libraries.
*
@@ -817,6 +879,16 @@ private class InteropTransformer(val context: Context, override val irFile: IrFi
return initializer.shallowCopy()
}
private fun generateCCall(expression: IrCall): IrExpression {
val function = expression.symbol.owner
context.llvmImports.add(function.llvmSymbolOrigin)
val exceptionMode = ForeignExceptionMode.byValue(
function.konanLibrary?.manifestProperties?.getProperty(ForeignExceptionMode.manifestKey)
)
return generateWithStubs(expression) { generateCCall(expression, builder, isInvoke = false, exceptionMode) }
}
override fun visitCall(expression: IrCall): IrExpression {
val intrinsicType = tryGetIntrinsicType(expression)
if (intrinsicType == IntrinsicType.OBJC_INIT_BY) {
@@ -858,11 +930,7 @@ private class InteropTransformer(val context: Context, override val irFile: IrFi
}
if (function.annotations.hasAnnotation(RuntimeNames.cCall)) {
context.llvmImports.add(function.llvmSymbolOrigin)
val exceptionMode = ForeignExceptionMode.byValue(
function.konanLibrary?.manifestProperties?.getProperty(ForeignExceptionMode.manifestKey)
)
return generateWithStubs { generateCCall(expression, builder, isInvoke = false, exceptionMode) }
return generateCCall(expression)
}
val failCompilation = { msg: String -> error(irFile, expression, msg) }
@@ -322,7 +322,8 @@ private class BackendChecker(val context: Context, val irFile: IrFile) : IrEleme
)
}
if (callee.returnType.isNativePointed(symbols))
if (callee.returnType.isNativePointed(symbols) &&
!callee.hasCCallAnnotation("CppClassConstructor"))
reportError(expression, "Native interop types constructors must not be called directly")
}
@@ -57,6 +57,7 @@ externalStdlibTestsDir.mkdirs()
ext.platformManager = project.project(":kotlin-native").platformManager
ext.target = platformManager.targetManager(project.testTarget).target
ext.llvmHome = platformManager.platform(target).configurables.absoluteLlvmHome
ext.testLibraryDir = "${ext.testOutputRoot}/klib/platform/${project.target.name}"
@@ -3824,13 +3825,29 @@ createInterop("leakMemoryWithRunningThread") {
}
createInterop("cppClass") {
if (isAppleTarget(project)) {
// TODO: For cpp we need a header with `new`.
it.extraOpts "-compiler-option", "-I$llvmHome/include/c++/v1"
}
it.defFile 'interop/cpp/cppClass.def'
}
createInterop("cppTypes") {
if (isAppleTarget(project)) {
// TODO: For cpp we need a header with `new`.
it.extraOpts "-compiler-option", "-I$llvmHome/include/c++/v1"
}
it.defFile 'interop/cpp/types.def'
}
createInterop("cppSkia") {
if (PlatformInfo.isMac()) {
// TODO: For cpp we need a header with `new`.
it.extraOpts "-compiler-option", "-I$llvmHome/include/c++/v1"
}
it.defFile 'interop/cpp/skia.def'
}
if (PlatformInfo.isAppleTarget(project)) {
createInterop("objcSmoke") {
it.defFile 'interop/objc/objcSmoke.def'
@@ -4202,6 +4219,13 @@ interopTest("interop_cppTypes") {
interop = 'cppTypes'
}
interopTest("interop_cppSkia") {
disabled = (project.testTarget == 'wasm32') // No interop for wasm yet.
source = "interop/cpp/skia.kt"
interop = 'cppSkia'
goldValue = "17 17\n"
}
/*
TODO: This test isn't run automatically
task interop_echo_server(type: RunInteropKonanTest) {
@@ -85,10 +85,12 @@ fun main() {
val testRun = FeatureTest()
testRun.ctorDefault()
testRun.ctorWithParam()
val a0 = retByValue(null)
println("a0.useContents {iPub} = ${a0.useContents {iPub}}" )
println("a0.useContents { foo() } = ${a0.useContents { foo() }}" )
// By value for C++ requires further design of stubs mechanism.
// So not supported for now.
//val a0 = retByValue(null)
//println("a0.useContents {iPub} = ${a0.useContents {iPub}}" )
//println("a0.useContents { foo() } = ${a0.useContents { foo() }}" )
// retByValue(null)!!.getValue().foo()
// val a1 = interpretPointed<CppTest>(retByValue(null).rawValue)
@@ -0,0 +1,41 @@
language = C++
compilerOpts = -std=c++17
plugin = org.jetbrains.kotlin.native.interop.skia
---
// TODO: this one checks the syntactic aspect for now.
// To be updated for proper c++ destructor and
// kotlin garbage collection interaction.
template <typename T> class sk_sp {
public:
sk_sp(T* obj) : data(obj) {}
T* release() {
return data;
}
private:
T* data;
};
template <typename T> sk_sp<T> sk_ref_sp(T* obj) {
return sk_sp<T>(obj);
}
class Value {
public:
int data;
};
class Foo {
public:
Foo() { }
virtual sk_sp<Value> foo(Value *obj) {
return sk_sp<Value>(obj);
}
virtual Value* bar(sk_sp<Value> obj) {
return obj.release();
}
};
@@ -0,0 +1,13 @@
import kotlinx.cinterop.*
import kotlin.test.*
import skia.*
fun main() {
val f = Foo()
val a = nativeHeap.alloc<Value>()
a.data = 17
val x = f.foo(a.ptr)
val z = f.bar(x)
println("${a?.data} ${z?.pointed?.data}")
}
@@ -3,7 +3,7 @@ import kotlin.test.*
import kotlin.random.*
import cpptypes.*
/*
@Test
fun test_retByValue(k: Int) {
memScoped {
@@ -11,6 +11,7 @@ fun test_retByValue(k: Int) {
assertEquals(k, x.get())
}
}
*/
@Test
fun test_retByPtr(k: Int) {
@@ -35,7 +36,7 @@ fun test_retByRefConst(k: Int) {
val x = interpretPointed<CppTest>(retByRefConst(k).rawValue)
assertEquals(k, x.get())
}
/*
@Test
fun test_paramByValue(k: Int) {
val x = nativeHeap.alloc<CppTest>() {}
@@ -43,7 +44,7 @@ fun test_paramByValue(k: Int) {
assertEquals(k, paramByValue(x.readValue()))
nativeHeap.free(x)
}
*/
@Test
fun test_paramByPtr(k: Int) {
val x = nativeHeap.alloc<CppTest>() {}
@@ -80,12 +81,12 @@ fun main() {
val seed = Random.nextInt()
val r = Random(seed)
test_retByValue(r.nextInt())
//test_retByValue(r.nextInt())
test_retByPtr(r.nextInt())
test_retByPtrConst(r.nextInt())
test_retByRef(r.nextInt())
test_retByRefConst(r.nextInt())
test_paramByValue(r.nextInt())
//test_paramByValue(r.nextInt())
test_paramByPtr(r.nextInt())
test_paramByPtrConst(r.nextInt())
test_paramByRef(r.nextInt())
+1
View File
@@ -218,6 +218,7 @@ dependencies {
distPack project(':kotlin-native:Interop:Runtime')
distPack project(':kotlin-native:Interop:Indexer')
distPack project(':kotlin-native:Interop:StubGenerator')
distPack project(':kotlin-native:Interop:Skia')
distPack project(':kotlin-native:backend.native')
distPack project(':kotlin-native:utilities:cli-runner')
distPack project(':kotlin-native:utilities:basic-utils')
@@ -59,6 +59,8 @@ object DefaultDeclarationHeaderRenderer : DeclarationHeaderRenderer {
val DEFAULT = DescriptorRenderer.COMPACT_WITH_SHORT_TYPES.withOptions {
modifiers = DescriptorRendererModifier.ALL
overrideRenderingPolicy = OverrideRenderingPolicy.RENDER_OVERRIDE
eachAnnotationOnNewLine = true
annotationArgumentsRenderingPolicy = AnnotationArgumentsRenderingPolicy.UNLESS_EMPTY
excludedAnnotationClasses += StandardNames.FqNames.suppress
-127
View File
@@ -1,127 +0,0 @@
Что я хотел, и что из этого вышло
--------------------------------------
Я думал, что быстро прикостыляю базовый С++ к cinterop, используя plain C обёртки над CXXMethod и передавая адрес объекта (receiver) дополнительным (скрытым) параметром. Так сделан C++ runtime и так сделана куча биндингов в C++ из разных языков. Так что - должно получиться.
Вышло сплошное разочаровние. Если обобщить все встреченные беды - вывод такой: существующий cinterop не годится для этого от слова "совсем". Что обидно: если бы я не тратил время на борьбу с cinterop - я бы, наверное, за это время написал прототип с нуля. Делать глубокий рефакторинг в рамках тестового задания я счёл неправильным - я не имею возможности даже обсудить грабли, тем более - протестить то что получится. (на самом деле, бОльшую часть кода и инфраструктуры cinterop можно переиспользовать, но рефаторинг коснётся всех сценариев - так что надо всё тестить)
Что получилось в итоге. Ну я всё же прикостылял базовый C++, не всё и с разной степенью проработки (C++ бесконечен, see below for details). Можно запустить тесты. Можно попытаться затащить библиотеку или, например, свои собственные legacy наработки. Я пытаюсь игнорировать неподдержанные (пока) типы и конструкции, но не все учтены, и на реальном примере придется подхачить C++, чтобы пройти компиляцию. Но это не проблема: дописать поддержку недостающего - вопрос времени/эффортов, принципиальных проблем нет. (В сторону: интероп с собственным legacy кодом я вижу как, потенциально, основной business case. У меня много знакомых плюсеров, которые сидят на легаси огромного объёма и были бы счастливы патчить бизнес-логику на более динамичном и функциональном языке).
Но пока результат разочаровывает, и вот почему:
* Этим неудобно пользоваться из-за разницы между CValue<CxxClass> и сгенерённым котлиновским CxxClass. (Никто и не пользуется: даже Indexer.kt всюду использует CValue, а сгенерённые классы остались без дела)
* cinterop всё время пытается делать свои копии нативных структур: readValue, useContents, placeTo etc. Это не работает для нетривиальных объектов. Хуже того: это абсолютный тупик для move семантики, которая в современном C++ является основным трендом. Пример: сейчас модно писать API, в котором все std::string передаются by value, в расчете на move. При попытке оттащить string в какую-то "другую" память (даже с использованием правильных move/copy конструкторов) я получаю маленький объект, из которого торчит указатель в C++ heap, причём указатель будет всё время меняться, immutability ему противопоказана. Или std::shared_ptr ... в общем, понятно. И. наконец, эти копирования не добавляют ничего полезного в случае kotlin-native (или я не понял).
* Похоже, cinterop был заточен на то, что основной workflow такой: аллоцировать в котлине memory block, инициализировать поля вручную, дальше побитово копировать туда где нужно. У объектного языка другой flow: get handler (using create or new), keep it and use, destroy. Я прикостылял placement new, чтобы сконструировать объект там где его аллоцировал котлин, но смысла в этом ноль. И это не работает для Factory, у объекта может не быть публичного конструктора.
* Я не стал фиксить by value параметры и return, это будет глючить на нетривиальных объектах (в тестах таких нет). Фикс простой, но концептуальный и задевает весь существующий cinterop.
Что я предлагаю сделать:
* Отказаться от попыток менеджерить нативные объекты. Это невозможно для объектов и не имеет смысла для kotlin-native. Используем handler с нативным указателем внутри. Котлиновский класс сейчас так и генерится - отлично. Никаких CValue. Параметр by value - компилятор подставит звёздочку, *rawPtr. Возврат by value: я использую placement new и copy ctor, чтобы заставить C++ вернуть значение не на стеке, а в хипе. Собственно, это сделано. Но дальше надо в котлин отдать handler с указателем в хип, а не readValue. Это тривиально и это я тоже сделал, но это ломает совместимость с существующим кодом.
* Упростить сгенерённый для котлина API, в смысле типов. Никаких CValue, ptr и прочего. Все параметры и возвращаемые типы - только котлиновский прокси класс, handler. Компилятор подставит нужное преобразование в значение, указатель или ссылку. Где-то здесь придётся покувыркаться с move и с temporary, но это feasible. Манглингом или типами показать разницу между const-nonconst параметрами (in/out). Разницы между by value и const ref вообще нет. В случае неоднозначности (конфликт имён из-за того, что не показываем в котлине разницу между by value и by ptr), и только в этом случае - дополнительно манглинг, или программист подскажет компилятору как-то, например напишет ptr.
* Вместо бесполезного для деструкторов mamscope сделать cxxscope, чтобы автоматически вызывать зарегистрированные деструкторы. Я попробовал сделать, используя std::unique_ptr - но не закончил.
TODO
----
0. Design style. Костыли. Во многих местах в реальной продуктовой задаче я бы предпочёл сначала сделать подготовительный рефакторинг, цикл review-commit-tests, потом поверх этого сделать фичу. Т.е. разделить коммит на два: изменение (без добавления) и добавление (без изменений). Для тестового задания я в основном выбирал вариант минимальных изменений, чтобы проще ревьюить за один раз. В результате - костыли и рост технического долга, но это осознанный выбор.
0. Сейчас могут сгенериться имена с конфликтами в двух популярных случаях: 1) const и non-const метод с одинаковой сигнатуройг 2) функции с похожей сигнатурой, одна получает параметр по указателю, другая по ссылке (оба биндятся как указатель). В первом случае: добавим mangling, во втором надо мапить LVReference в not nullable, и всё развяжется. Руки не дошли.
0. NativeIndex имеет плоскую структуру создаваемых сущностей. Хотелось иметь дерево, так проще поддержать вложенные классы и неймспейсы. Не успел. В результате вложенные структуры я сделал простым манглингом (quick and dirty). Это работает, но выглядит ужасно, надо переделать. В качестве варианта я рассматривал идею: оставить indexer плоским, но пусть он содержит только один уровень семантической вложенности (один уровень semantic parent в терминологии clang), и я буду строить дерево из множества маленьких одноуровневых индексеров.
0. Хотелось замапить namespace в package. Вроде просто, но уткнулся в тяжелый рефакторинг в генераторах: там единственность выходного файла и package прибита гвоздями. Один translation unit = один выходной файл. А тут translation unit должен создать целое дерево с перекрестными импортами.
0. Многие типы и конструкции не поддержаны, но каких-то принципиальных проблем в реализации я не вижу. Темплейты - почти мапятся в темплейты, а инстанцированный темплейт - это обычный класс. Exceptions надо просто ретранслировать. RValueRef кажется должен работать легко. Операторы по разному... их много и это надолго. Можно мапить в обычные функции. Но например operator-> я бы предложил не мудрить, а вместо этого написать удобный аналог unique_ptr и shared_ptr вручную, и тогда их почти не останется. Как мапить лямбду в блок и обратно, похоже без поддержки рантайма не обойдется. И т.д., фичи бесконечны...
0. Override С++ методов в котлине (callbacks) Можно сделать через обертки: генерим C++ класс-наследник, у которого каждый метод перевызывает соответствующий котлинвский метод как plain C. Это сработает, но сгенерим и скомпилируем много лишнего. Хорошо бы генерить эти обертки только on demand, т.е. когда компилятор видит, что котлин хотел бы перегрузить соответствующий метод. Но замечу, что в современном C++ для колбэка скорее будут использовать лямбду и std::function, чем наследование.
0. Надо замапить самые важные std типы: string, containers, smart pointers
0. Сейчас работа с C/C++ из котлина выглядит неестественно, многословная. Interop должен быть seamless. Без лишних interpretPointed, rawValue etc. Но это требует определенного пересмотра концепции (type matching). Уже сказал об этом выше.
0. Костыль visitChildren, или франкенштейн из двух параллельных indexDeclaration.
Проблема в том, что IndexDeclaration(CXIdxDeclInfo) не отличает класс от темплейта (и функцию от темплейтной функции). Курсор внутри дерева indexDeclaration для темплейтов имеет kind = CXCursor_ClassDecl (или StructDecl) вместо ожидаемого CXCursor_ClassTemplate, и я не нашел способа вытащить "правильный" курсор. Но если разобрать дерево через visitChildren - то курсор правильный. Поэтому, если язык C++, то я разбираю классы через visitChildren (и отбрасываю темплейты, пока они не поддержаны). Я временно оставил такого франкенштейна, когда часть сущностей парсится через CXIdxDeclInfo (из clang_indexTranslationUnit), а часть через CXCursor (из visitChildren). Я думаю, что надо всё перенести в visitChildren, но у меня не было возможности протестить все сценарии в plain C и ObjC. Такой рефакторинг - не для тестового задания. Кстати, такой костыль уже был до меня, для парсинга ObjC, но я не стал втыкать свой код в него, а написал отдельный - потому что мне нужен был CXChildVisit_Recurse, а там был CXChildVisit_Continue и я опять боялся сломать ObjC, который не умею как следует протестить.
Conceptual
----------
0. Internal linkage.
Сейчас сущности с internal linkage (static & anon namespace) не фильтруются, биндинг на них создаётся (все функции в .def в тестах написаны со словом static). Фактически, строим так, как будто C включён в котлиновский исходник. В таком случае, эти сущности должны биндиться как internal, причем каждый С-ник надо компилировать, как отдельный translation unit, иначе будут конфликты.
- Это нормальный подход, если моё намерение - расширить (extend) нативную имплементацию на котлине.
- Если моё намерение - затащить нативную библиотеку, то я компилирую все публичные хедеры как один translation unit, при этом лучше зафильтровать static.
- Проблема некритична, потому что в публичных хедерах static и anon namespace не встречается. Зато часто в c/cpp имплементации. Лучше быть последовательным в этом вопросе.
0. Что делать с protected и private? Я пока выключил. Технически, сейчас котлиновский класс соответствует C++ ссылке на объект, а не derived class, т.е. должен иметь доступ только к public. IMHO: это неправильно, в такой логике невозможно реализовать C++ callbacks (оверрайдить виртуальные методы, в т.ч. приватные). See below.
0. Derive vs Use. Хотелось бы имитироватиь наследование от C++ класса. Это можно сделать в концепции kotlin object is a handler, через делегирование, но надо решить вопрос автоматического вызова деструктора на созданном объекте.
0. Конструкторы-деструкторы надо доработать. Вопрос концептуальный, требует обсуждения. И это довольно большая работа. Моё предложение: надо различать 3 сущности:
- Явно аллоцированный объект с владением: alloc + init (т.е. placement new) и парный к нему destroy (это this->~MyClass()), деаллокация автоматическая или ручная
- Holder, владеющий указатель на объект, созданный в нативном хипе по new MyClass() и парный к нему delete. Аналог std::unique_ptr. Семантически не отличается от предыдущего.
- Non-owning holder без передачи ownership, т.е. невладеюший указатель. Освобождать не надо. Аналог C raw pointer.
- Можно замапить std::shared_ptr на котлин тип с acquire/release парой.
Design issues
-------------
0. В контейнере members может лежать IncompleteField. Это имело бы смысл, если Incomplete - такой lazy тип, который позже можно, например, зарезолвить. Но это не предусмотрено - так что зачем добавлять dummy, надо было просто пропустить. C FunctionDecl ещё хуже: в registry складываем неподдерживаемые функции, их отличие только в том, что у них может быть UnsupportedType у параметра. В результате exception в stub builder'е. Я заменил такие невалидные объекты на null, их легче потом фильтровать. Но вообще-то их добавлять не следовало. Проблема в том, как сделано добавление: через getOrPut. Если я только в середине построения объекта понял, что валидный объект создать не получается - он всё равно уже добавлен в map, я уже внутри initializer'а. И это не эксепшн концептуально, потому что это нормальная часть контракта, не исключение. Здесь лучше подошла бы логика factory: пытаюсь создать объект и добавляю только если создался успешно.
0. Часто приватный метод класса является, по сути, чистой функцией и не работает со стейтом объекта - только с параметром. Такие функции надо вытаскивать из класса и делать чистой функцией, или экстеншном на типе параметра. Пример:. getArrayLength(type: ArrayType) сделана приватным методом StructStubBuilder'а, но никакого отношения к этому классу не имеет. Таких примеров много.
0. Спорное решение про throw Error("Native interop types constructors must not be called directly"). В чем смысл? Мой тип - handler, и это его конструктор. Точно так работает `std::make_unique` и `make_shared`, при этом я могу и конструктором воспользоваться: `auto x = unique_ptr<T>(new T())`. Другого способа сделать биндинг на класс, у которого только factory method - вообще нет. interpretPointed по сути то же самое, но непонятно, и зачем плодить сущности? Наконец, это runtime exception, а не синтаксическая ошибка, которую мог бы подсветить IDE - и это неудобно.
В порядке эксперимента я отключил этот error, в результате и клиентский код, и генеренный стал проще и читабельнее. От чего защищаемся, ведь я все равно могу написать такой wrapper вручную, хранить как long то, что вернул фактори метод, а потом вызывать методы класса на нём? Пусть лучше это сделает генератор.
0. Parameter by value.
На входе - C declaration:
```
struct CTest {};
int paramByValue(struct CTest x);
```
На выходе
```
fun paramByValue(x: CValue<CTest>): Int {
memScoped {
return kniBridge0(x.getPointer(memScope).rawValue)
}
}
int32_t c1_kniBridge0 (void* p0) {
return (int32_t)paramByValue(*(struct CTest*)p0);
}
```
Здесь копия сделана дважды: сначала CValue.getPointer, а потом при вызове paramByValue. Причем во втором случае C++ компилятор корректно использует конструктор копирования, а getPointer - увы...
Нет, это еще не всё. Поскольку функция принимает CValue, а у меня в руках есть только `class CTest`, который `CStructVar`, то придется сделать третье копирование уже в клиентском коде: readValue.
При возврате то же самое:
```
void c1_kniBridge1 (void* p0) {
struct CTest kniStructResult = retByValue();
memcpy(p0, &kniStructResult, sizeof(kniStructResult));
}
```
Здесь уже два копирования. Достаточно было бы
```
void c1_kniBridge1 (void* p0) {
*(struct CTest*) p0 = retByValue();
}
```
(Я знаю, что это не сработает при наличии const fields, но это значит, что автор явно запретил копирование. Такую структуру на C невозможно создать в хипе, только на стеке - такой "тёмный угол" стандарта C. На C++ это решается с помощью placement new.)
Но есть ещё и третье копирование в котлиновской обёртке:
```
return kniRetVal.readValue()
```
и это ещё не конец: kniRetVal это CVariable, так что readValue делает копию дважды. Четыре копирования на каждый вызов retByValue - только чтобы получить экземпляр структуры.
0. Я попытался постичь дзен этого пассажа:
```
* [CValuesRef] is designed to be used as Kotlin representation of pointer-typed parameters of C functions.
* When passing [CPointer] as [CValuesRef] to the Kotlin binding method, the C function receives exactly this pointer.
* Passing [CValues] has nearly the same semantics as passing by value: the C function receives
* the pointer to the temporary copy of these values, and the caller can't observe the modifications to this copy.
```
Первые две фразы - ok. Третью я не смог расшифровать. `Passing [CValues]` - куда passing? Если C API принимает MyStruct* то это мутатор с input параметром и отдавать туда нужно указатель на исходный объект. Иначе в чём смысл - вызывать мутатор на временном объекте? Если параметр MyStruct const* то компилятор уже позаботился о нас (трюки типа mutable или const_cast не в счёт - тот кто делает это в публичном API - сам берет на себя ответственность). Если параметр по значению, то C компилятор и так сделает копию.
-8
View File
@@ -1,8 +0,0 @@
Build native:
g++ -std=c++14 -o src/native/features.o -c src/native/features.cpp
c++ interop:
../../dist/bin/cinterop -def features.def -compiler-options "-I." -o features
@@ -1,6 +0,0 @@
headers = src/native/features.h
headerFilter = src/native/features.h
package = test
language = C++
#compilerOpts = -std=c++14
@@ -1,176 +0,0 @@
//package test.cpp
import kotlinx.cinterop.*
import platform.posix.*
import platform.posix.memcpy
import test.*
import kotlin.test.*
class FeatureTest {
@Test fun ctorDefault() {
memScoped {
val x = alloc<ns__CppTest>()
ns__CppTest.__init__(x.ptr)
assertEquals(42, x.iPub)
assertEquals(42, x.foo(null))
assertEquals(43, x.foo(x.ptr))
// dtor is not called, leak is intentional for the purpose of UT
}
}
@Test fun ctorWithParam() {
memScoped {
val x = alloc<ns__CppTest>()
ns__CppTest.__init__(x.ptr, 1001, 0.0)
assertEquals(1001, x.iPub)
assertEquals(1001, x.foo(null))
assertEquals(1002, x.foo(x.ptr))
}
}
@Test fun copyCtor(y: ns__CppTest) {
val x = nativeHeap.alloc<ns__CppTest>() {}
ns__CppTest.__init__(x.ptr, y.ptr)
assertEquals(y.iPub, x.iPub)
nativeHeap.free(x)
}
@Test fun reinitWithCtorAndDtor(y: ns__CppTest) {
val count = ns__CppTest.getCount()
val x = nativeHeap.alloc<ns__CppTest>() {}
ns__CppTest.__init__(x.ptr, y.ptr)
assertEquals(ns__CppTest.getCount(), count + 1)
assertEquals(y.iPub, x.iPub)
ns__CppTest.__destroy__(x.ptr)
y.iPub = -11
assertEquals(y.iPub, -11)
ns__CppTest.__init__(x.ptr, y.ptr)
assertEquals(x.iPub, -11)
ns__CppTest.__destroy__(x.ptr)
assertEquals(ns__CppTest.getCount(), count)
nativeHeap.free(x)
}
@Test fun publicField(x : ns__CppTest) {
x.iPub = 21
assertEquals(22, x.foo(x.ptr))
}
@Test fun lvRefParameter() {
memScoped {
val x = alloc<ns__NoName>()
var i = alloc<IntVar>()
i.value = 758
assertEquals(x.noNameMember(i.ptr), 759)
assertEquals(i.value, 759)
}
}
@Test fun staticField() {
val save = ns__CppTest.getCount()
assertEquals(ns__CppTest.getCount(), ns__CppTest.counter)
ns__CppTest.counter = 654
assertEquals(ns__CppTest.getCount(), 654)
assertEquals(ns__CppTest.getCount(), ns__CppTest.counter)
ns__CppTest.counter = save
assertEquals(ns__CppTest.getCount(), save)
}
}
fun main() {
val testRun = FeatureTest()
testRun.ctorDefault()
testRun.ctorWithParam()
val a1 = interpretPointed<ns__CppTest>(ns__create().rawValue)
testRun.publicField(a1)
testRun.staticField()
testRun.lvRefParameter()
a1.iPub = 112
testRun.copyCtor(a1)
testRun.reinitWithCtorAndDtor(a1)
//******************************
println("*** UT passed ***")
//******************************
testStatic()
testCtor()
// testCtor1()
// testCtor2()
// testCtor3()
test2()
}
fun testStatic() {
println("ns__CppTest.s_fun() returns ${ns__CppTest.s_fun()}")
println("ns__CppTest.s_fun() returns ${ns__CppTest.s_fun()}")
println("ns__CppTest.s_fun() returns ${ns__CppTest.s_fun()}")
}
fun testCtor() {
println("testCtor")
val cxx = nativeHeap.alloc<ns__CppTest>() {
memcpy(ptr, ns__create(), typeOf<ns__CppTest>().size.convert()) // use placement new here
}
cxx.foo(null)
nativeHeap.free(cxx)
}
/*
fun testCtor1() {
println("testCtor1: interpretPointed<ns__CppTest>(ns__CppTest.__create__().rawValue)")
val theStruct = interpretPointed<ns__CppTest>(ns__CppTest.__create__().rawValue)
theStruct.iPub = 33
theStruct.foo(theStruct.ptr)
println("testCtor1: ns__CppTest(ns__CppTest.__create__().rawValue)")
val xs = ns__CppTest(ns__CppTest.__create__().rawValue)
xs.foo(null)
xs.foo(xs.ptr)
println("testCtor1: ns__CppTest(ns__CppTest.__create__(1001).rawValue)")
val x2 = ns__CppTest(ns__CppTest.__create__(1001, 2.718).rawValue)
x2.foo(null)
x2.foo(x2.ptr)
}
fun testCtor2() {
println("testCtor2: ns__CppTest(ns__create().rawValue)")
val xs = ns__CppTest(ns__create().rawValue)
xs.foo(null)
xs.foo(xs.ptr)
}
fun testCtor3() {
println("testCtor3: MyStruct()")
val xs = MyStruct()
xs.foo()
}
*/
fun test2() {
println("test2")
val x = ns__bar(null)
// val theS = interpretPointed<ns__CppTest>(ns__bar(null).rawValue)
// theS.foo(null)
println("x.useContents {iPub} = ${x.useContents {iPub}}" )
}
@@ -1,73 +0,0 @@
#include "features.h"
#include <iostream>
using namespace std;
namespace ns {
int NoName::noNameMember(int& iRef) {
cout << __PRETTY_FUNCTION__ << " invoked" << endl;
return ++iRef;
}
int CppTest::counter;
int CppTest::s_fun() {
static int counter = 777;
cout << __PRETTY_FUNCTION__ << " invoked" << endl;
return counter++;
}
int CppTest::foo(const CppTest* x) {
int res = x == this;
cout << "This is CppTest::foo: result is iPub + (int)(param == this): " << iPub + res << endl;
return iPub + res;
}
CppTest::CppTest() {
cout << ++counter << "\t" << __PRETTY_FUNCTION__ << endl;
}
CppTest::CppTest(const CppTest& c) {
*this = c;
cout << ++counter << "\t" << __PRETTY_FUNCTION__ << endl;
}
CppTest::CppTest(int i, double j) : iPub(i) {
cout << ++counter << "\t" << __PRETTY_FUNCTION__ << endl;
}
CppTest::~CppTest() {
cout << --counter << "\t" << __PRETTY_FUNCTION__ << endl;
}
CppTest bar(CppTest* s) {
if (s)
return *s;
else
return * new CppTest();
}
CppTest* create() {
return new CppTest();
}
} // ns
CppTest* create() {
cout << __PRETTY_FUNCTION__ << " declared in global ns" << endl;
return nullptr;
}
::CppTest* ns2::create() {
cout << __PRETTY_FUNCTION__ << " declared in ns2" << endl;
return nullptr;
}
void test() {
char buf[sizeof(ns::CppTest)];
ns::CppTest* x = new((ns::CppTest*)buf) ns::CppTest();
}
@@ -1,211 +0,0 @@
class MyClass {
};
MyClass retByValue();
struct OuterCStruct {
const int plainCField;
struct {
int innerCField;
};
union {
int innerUnionF1;
double innerUnionF2;
};
struct Inner {
};
struct Inner innerImpl;
#ifdef __cplusplus
// Illegal C. ok in C++
typedef struct {
} NestedStructInnerT;
NestedStructInnerT nestedStructT;
#endif
};
const int g_IntVal = 0;
typedef int (*funT)(int);
void fooFuncParam( funT );
void barFuncParam( int (*)(int) );
template <typename T> struct TmplStruct {
public:
void baz() const {}
};
void funTmplParam(TmplStruct<int>& t);
template <class X> void tmplFunction();
template <class X> void tmplFunction(X x);
//int funVariadic(const char* format, ...);
//#include <cstdarg>
//void appendVAList(const char format[], va_list); // doesn't work
int plainCFreeFunction();
static inline void plainCInternalFunction(); // [Conceptual] should be mapped as internal fun or not eligible for binding at all
int g_Var;
struct PlainCStruct {
int plainCField;
struct {
int innerCField;
};
};
typedef struct PlainCStruct PlainCStructT;
using PlainCStructAlias = PlainCStruct;
void wrappingFun() {
class NestedInFunction {
int varInLocalClass;
} var; // local types shall be ignored
}
struct UnknownT;
UnknownT* cFunUnknownParams(const UnknownT*);
struct ForwardT; // declaratuin
ForwardT* cFunForwardParams(const ForwardT*); // decl
struct ForwardT {}; // definition
ForwardT* cFunForwardParams(const ForwardT*) { // def
return new ForwardT();
}
namespace ns0 {
class clDeclaredInNS;
extern int varWithDefinition;
int varWithDefFirst = 99;
}
namespace ns0_alias = ns0;
class ns0_alias::clDeclaredInNS{};
extern int ns0_alias::varWithDefFirst;
using clDeclaredInNSAlias = ns0_alias::clDeclaredInNS;
// int ns0_alias::varInNSAlias; // illegal declaration
int ns0_alias::varWithDefinition = 21;
namespace ns {
int g_varInNS;
namespace {
void funInInnerAnonNS();
namespace nsInAnonNS {
int memberOf_nsInAnonNS;
}
}
namespace NestedNS {
int funInNestedNS();
int g_varInNestedNS;
}
namespace {
void funInInnerAnonNS();
}
typedef class {
public:
int noNameMember(int& iRef);
// int noNameMember(const int& iRef); need mangling
static int noNameStaticFun(); // won't work
int fieldInAnonClass;
// static int s_fieldInAnonClass; not legal C++
} NoName;
enum EnumInNS {one, two, three};
enum class EnumClass : char { one, two, three};
class CppTest {
public:
static int s_fun();
CppTest();
CppTest(const CppTest&);
explicit CppTest(int i, double j = 3.14);
~CppTest();
operator NoName() const;
int iPub = 42;
virtual int foo(const CppTest*);
// virtual int foo(const CppTest*) const;
static int counter;
static NoName compStaticField;
NoName compField;
static int getCount() { return counter; }
template <class X> void fooTmplMember() const;
class Nested {
public:
int nestedFoo();
};
enum NestedEnum {one, two, three};
void funEnumParam(NestedEnum e);
private:
CppTest* funPrivate() const;
static int s_funPrivate();
private:
int iPriv;
};
CppTest funRetByValue(CppTest* s);
CppTest* create();
} // ns
namespace {
ns::CppTest* fooInAnonNamespace();
}
class CppTest;
CppTest* create();
namespace ns2 {
::CppTest* create();
} // ns2
/*
template <typename T> class TmplClass {
public:
void baz() const {}
};
typedef class TmplStruct<int> IntTmplStruct;
IntTmplStruct intTmplStruct;
struct Smth {
IntTmplStruct intTmplStruct;
};
*/
-6
View File
@@ -1,6 +0,0 @@
g++ -std=c++14 main.cpp -I$HOME/work/cpptools/skia /Users/vdi/work/cpptools/skia/out/Static/libskia.a -framework CoreServices -framework CoreText -framework CoreGraphics
../../dist/bin/cinterop -def Skia.def -o Skia -compiler-options "-I/Volumes/vdi/work/cpptools/skia"
../../dist/bin/kotlinc src/skiaMain/kotlin/test_SkString.kt -o test_SkString -l Skia -linker-options "$HOME/work/cpptools/skia/out/Static/libskia.a" -linker-options "-framework CoreServices" -linker-options "-framework CoreText" -linker-options "-framework CoreGraphics"
-5
View File
@@ -1,5 +0,0 @@
headers = include/core/SkString.h include/core/SkTime.h
headerFilter = include/core/SkTime.h include/core/SkString.h
package = skia
compilerOpts = -std=c++14
compilerOpts = -x c++
-11
View File
@@ -1,11 +0,0 @@
#include "include/core/SkTime.h"
#include <iostream>
using namespace std;
int main()
{
auto nsec = SkTime::GetNSecs();
cout << nsec << endl;
return 0;
}
@@ -1,19 +0,0 @@
import kotlinx.cinterop.*
import SkTime.*
fun main() {
println("Skia sample")
println("${SkTime.GetNSecs()}")
println("${SkTime.GetNSecs()}")
println("${SkTime.GetNSecs()}")
test_GetDateTime()
}
fun test_GetDateTime() {
memScoped {
val dateTime = alloc<SkTime__DateTime>()
SkTime.GetDateTime(dateTime.ptr)
println("${dateTime.fYear}-${dateTime.fMonth}-${dateTime.fDay} ${dateTime.fHour}:${dateTime.fMinute}.${dateTime.fSecond}" )
}
}
@@ -1,80 +0,0 @@
import kotlinx.cinterop.*
import kotlin.test.*
import skia.*
fun hello() {
memScoped {
val cString = "Hello Skia".cstr.getPointer(memScope)
val s = alloc<SkString>() {
SkString.__init__(ptr, cString)
}
println(s.c_str()?.toKString())
}
}
fun SkString_create(from: String): SkString {
val res = nativeHeap.alloc<SkString>() {}
memScoped {
val cString = from.cstr.getPointer(memScope)
SkString.__init__(res.ptr, cString)
}
return res
}
fun SkString.clone(): SkString {
return nativeHeap.alloc<SkString>() {
SkString.__init__(ptr, this@clone.ptr)
}
}
fun SkString.delete(): Unit {
SkString.__destroy__(this.ptr)
nativeHeap.free(this)
}
fun SkString.toKString() = this.c_str()!!.toKString()
fun main() {
hello()
/*
with (CppContext) {
val go = SkString("Let's go fishing!")
val goback = SkString(go)
val pos = goback.find("fish")
println(go)
if (pos >= 0) {
goback.insert(pos, "back!")
goback.resize((pos + "back!".length)
}
println(goback)
oback.swap(go)
goback.resize(goback.size() - 1U)
goback.append(" again!")
println(goback)
}
*/
val go = SkString_create("Let's go fishing!")
println(go.toKString())
val goback = go.clone()
memScoped {
val pos = goback.find("fish".cstr.getPointer(memScope))
if (pos >= 0) {
goback.insert(pos.convert<ULong>(), "back!".cstr.getPointer(memScope))
goback.resize((pos + "back!".length).convert<ULong>())
}
println(goback.toKString())
goback.swap(go.ptr)
goback.resize(goback.size() - 1U)
goback.append(" again!".cstr.getPointer(memScope))
println(goback.toKString())
}
go.delete()
goback.delete()
}
@@ -118,6 +118,10 @@ class DefFile(val file:File?, val config:DefFileConfig, val manifestAddendProper
properties.getProperty("foreignExceptionMode")
}
val pluginName by lazy {
properties.getProperty("plugin")
}
}
}
+1
View File
@@ -598,6 +598,7 @@ if (buildProperties.isKotlinNativeEnabled) {
include ':kotlin-native:Interop:Runtime'
include ':kotlin-native:Interop:Indexer'
include ':kotlin-native:Interop:JsRuntime'
include ':kotlin-native:Interop:Skia'
include ':kotlin-native:utilities:basic-utils'
include ':kotlin-native:utilities:cli-runner'
include ':kotlin-native:klib'