diff --git a/kotlin-native/Interop/Indexer/build.gradle.kts b/kotlin-native/Interop/Indexer/build.gradle.kts index 49b9ce887bd..07f03a46781 100644 --- a/kotlin-native/Interop/Indexer/build.gradle.kts +++ b/kotlin-native/Interop/Indexer/build.gradle.kts @@ -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")) diff --git a/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt b/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt index 5b576cf8384..8091792184d 100644 --- a/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt +++ b/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt @@ -76,31 +76,7 @@ private class ObjCCategoryImpl( override val properties = mutableListOf() } - -private fun getParentName(cursor: CValue, pkg: List = emptyList()) : String? { // }: List? { - // 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 - ): 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, clazz: StructDefImpl) { + private fun addCxxMembers(classCursor: CValue, 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, structType: CValue, containerType: CValue) { - 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, typeAttributes: CValue? = null): Type { + open fun convertType(type: CValue, typeAttributes: CValue? = 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): Unit { + fun indexCxxDeclaration(cursor: CValue): 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, pkg: List = 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, 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() - 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): Boolean { - + protected open fun isFuncDeclEligible(cursor: CValue): 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): List { + private fun getFunctionParameters(cursor: CValue): List? { 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, _ -> diff --git a/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt b/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt index bcaef2be12c..07ba3300573 100644 --- a/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt +++ b/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt @@ -62,7 +62,6 @@ data class CompilationWithPCH( override val compilerArgs: List, override val language: Language ) : Compilation { - constructor(compilerArgs: List, precompiledHeader: String, language: Language) : this(compilerArgs + listOf("-include-pch", precompiledHeader), language) @@ -256,25 +255,20 @@ class FunctionDecl(val name: String, val parameters: List, 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 diff --git a/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Utils.kt b/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Utils.kt index f6638e5344a..614363162d6 100644 --- a/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Utils.kt +++ b/kotlin-native/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Utils.kt @@ -26,17 +26,17 @@ import java.nio.file.Paths import java.security.DigestInputStream import java.security.MessageDigest -internal val CValue.kind: CXTypeKind get() = this.useContents { kind } +val CValue.kind: CXTypeKind get() = this.useContents { kind } -internal val CValue.kind: CXCursorKind get() = this.useContents { kind } +val CValue.kind: CXCursorKind get() = this.useContents { kind } internal val CValue.type: CValue get() = clang_getCursorType(this) -internal val CValue.spelling: String get() = clang_getCursorSpelling(this).convertAndDispose() +val CValue.spelling: String get() = clang_getCursorSpelling(this).convertAndDispose() internal val CValue.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.isPublic: Boolean get() { +internal val CValue.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.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.isRecursivelyPublic(): Boolean { +internal fun CValue.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.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, parent: CValue) -> CXChildVisitResult -internal fun visitChildren(parent: CValue, visitor: CursorVisitor) { +fun visitChildren(parent: CValue, 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): Boolean { diff --git a/kotlin-native/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/internal/Annotations.kt b/kotlin-native/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/internal/Annotations.kt index 790dbdcd510..ed8e5697b17 100644 --- a/kotlin-native/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/internal/Annotations.kt +++ b/kotlin-native/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/internal/Annotations.kt @@ -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) \ No newline at end of file +public annotation class CEnumVarTypeSize(val size: Int) diff --git a/kotlin-native/CPLUSPLUS.md b/kotlin-native/Interop/Skia/SKIA_PLUGIN.md similarity index 83% rename from kotlin-native/CPLUSPLUS.md rename to kotlin-native/Interop/Skia/SKIA_PLUGIN.md index 569fc35f2dc..512233675d3 100644 --- a/kotlin-native/CPLUSPLUS.md +++ b/kotlin-native/Interop/Skia/SKIA_PLUGIN.md @@ -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 ------------ diff --git a/kotlin-native/Interop/Skia/build.gradle b/kotlin-native/Interop/Skia/build.gradle new file mode 100644 index 00000000000..27686710258 --- /dev/null +++ b/kotlin-native/Interop/Skia/build.gradle @@ -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 + } +} diff --git a/kotlin-native/Interop/Skia/src/main/kotlin/org/jetbrains/kotlin/native/interop/skia/SkiaIndexer.kt b/kotlin-native/Interop/Skia/src/main/kotlin/org/jetbrains/kotlin/native/interop/skia/SkiaIndexer.kt new file mode 100644 index 00000000000..ae814a937ef --- /dev/null +++ b/kotlin-native/Interop/Skia/src/main/kotlin/org/jetbrains/kotlin/native/interop/skia/SkiaIndexer.kt @@ -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, typeAttributes: CValue?): 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): Boolean = + cursor.containsOnlySkiaSharedPointerTemplates() + + override fun String.isUnknownTemplate() = // TODO: this is a hack. + this.isCppTemplate && !this.isSkiaSharedPointer +} + +fun CValue.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.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(">") \ No newline at end of file diff --git a/kotlin-native/Interop/Skia/src/main/kotlin/org/jetbrains/kotlin/native/interop/skia/SkiaPlugin.kt b/kotlin-native/Interop/Skia/src/main/kotlin/org/jetbrains/kotlin/native/interop/skia/SkiaPlugin.kt new file mode 100644 index 00000000000..6bb81d9f113 --- /dev/null +++ b/kotlin-native/Interop/Skia/src/main/kotlin/org/jetbrains/kotlin/native/interop/skia/SkiaPlugin.kt @@ -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) +} \ No newline at end of file diff --git a/kotlin-native/Interop/Skia/src/main/kotlin/org/jetbrains/kotlin/native/interop/skia/SkiaStubsBuildingContextImpl.kt b/kotlin-native/Interop/Skia/src/main/kotlin/org/jetbrains/kotlin/native/interop/skia/SkiaStubsBuildingContextImpl.kt new file mode 100644 index 00000000000..b7fb4a4d1ed --- /dev/null +++ b/kotlin-native/Interop/Skia/src/main/kotlin/org/jetbrains/kotlin/native/interop/skia/SkiaStubsBuildingContextImpl.kt @@ -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) + } + } +} \ No newline at end of file diff --git a/kotlin-native/Interop/StubGenerator/build.gradle b/kotlin-native/Interop/StubGenerator/build.gradle index 8bded182173..e59abf614e5 100644 --- a/kotlin-native/Interop/StubGenerator/build.gradle +++ b/kotlin-native/Interop/StubGenerator/build.gradle @@ -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)) } } -} \ No newline at end of file +} diff --git a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/CWrapperGenerator.kt b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/CWrapperGenerator.kt index 8de4751a46d..5082b466b42 100644 --- a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/CWrapperGenerator.kt +++ b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/CWrapperGenerator.kt @@ -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) +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 = listOf( - "const void* $symbol __asm(${symbol.quoteAsKotlinLiteral()});", - "const void* $symbol = &$function;" - ) + private fun bindSymbolToFunction(symbol: String, function: String): List { + 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 = 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 { + 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 { + 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) diff --git a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/MappingBridgeGeneratorImpl.kt b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/MappingBridgeGeneratorImpl.kt index 3632850eff1..cfda59df17d 100644 --- a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/MappingBridgeGeneratorImpl.kt +++ b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/MappingBridgeGeneratorImpl.kt @@ -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() - 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 } -} +} \ No newline at end of file diff --git a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/Mappings.kt b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/Mappings.kt index 80b9beada07..6d6f8e8c148 100644 --- a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/Mappings.kt +++ b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/Mappings.kt @@ -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 + ) } } diff --git a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StructRendering.kt b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StructRendering.kt index 4f440de86d1..5fae4e5fc0b 100644 --- a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StructRendering.kt +++ b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StructRendering.kt @@ -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? { diff --git a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIr.kt b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIr.kt index 27be99cbac5..3d5af9d9d6e 100644 --- a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIr.kt +++ b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIr.kt @@ -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) : diff --git a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrBridgeBuilder.kt b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrBridgeBuilder.kt index d6a602ef2d8..e30ad7ad7a1 100644 --- a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrBridgeBuilder.kt +++ b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrBridgeBuilder.kt @@ -63,7 +63,7 @@ class StubIrBridgeBuilder( ) private val mappingBridgeGenerator: MappingBridgeGenerator = - MappingBridgeGeneratorImpl(declarationMapper, simpleBridgeGenerator, context.libraryForCStubs.language) + MappingBridgeGeneratorImpl(declarationMapper, simpleBridgeGenerator) private val propertyAccessorBridgeBodies = mutableMapOf() private val functionBridgeBodies = mutableMapOf>() @@ -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() diff --git a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrBuilder.kt b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrBuilder.kt index f8c6a29d40c..23dc2840522 100644 --- a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrBuilder.kt +++ b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrBuilder.kt @@ -124,7 +124,7 @@ interface StubsBuildingContext { fun getKotlinClassForPointed(structDecl: StructDecl): Classifier - fun isOverloading(func: FunctionDecl): Boolean + fun isOverloading(name: String, types: List): Boolean } /** @@ -136,7 +136,7 @@ internal interface StubElementBuilder { fun build(): List } -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() - override fun isOverloading(func: FunctionDecl) = !uniqFunctions.add(func.name) // TODO: params & return type. + + override fun isOverloading(name: String, types: List):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() - 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 = (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 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) } diff --git a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrDriver.kt b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrDriver.kt index 4aca5934c11..b4586ec6d7f 100644 --- a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrDriver.kt +++ b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrDriver.kt @@ -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().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) diff --git a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrElementBuilders.kt b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrElementBuilders.kt index 61ccc77da71..f6c3cbb8ca8 100644 --- a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrElementBuilders.kt +++ b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrElementBuilders.kt @@ -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 = + val methods: List = 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).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 = + val classMethods: List = def.methods .filter { !it.isCxxInstanceMethod } .map { func -> try { - (FunctionStubBuilder(context, func).build() as List).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 = + 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).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 { - val platform = context.platform - val parameters = mutableListOf() + abstract override fun build(): List + fun buildParameters(parameters: MutableList, 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 - 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 { + val platform = context.platform + val parameters = mutableListOf() + + 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 + 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 { + if (context.configuration.library.language != Language.CPP) return emptyList() // TODO: Should we assert here? + + val platform = context.platform + val parameters = mutableListOf() + + 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)) + } } } } diff --git a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrExtensions.kt b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrExtensions.kt index 282e36848b9..5f81d7fa5b1 100644 --- a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrExtensions.kt +++ b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrExtensions.kt @@ -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 diff --git a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrMetadataEmitter.kt b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrMetadataEmitter.kt index 854b09a89da..daa1f6930c3 100644 --- a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrMetadataEmitter.kt +++ b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrMetadataEmitter.kt @@ -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) } diff --git a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrTextEmitter.kt b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrTextEmitter.kt index dc572818a81..62a37c5b747 100644 --- a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrTextEmitter.kt +++ b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrTextEmitter.kt @@ -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 -> diff --git a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/TypeUtils.kt b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/TypeUtils.kt index 8bc3a60255f..672d8896db9 100644 --- a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/TypeUtils.kt +++ b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/TypeUtils.kt @@ -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() } diff --git a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/CommandLine.kt b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/CommandLine.kt index b723d94dcff..0b4b9777d01 100644 --- a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/CommandLine.kt +++ b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/CommandLine.kt @@ -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: ") + + val dumpBridges by argParser.option(ArgType.Boolean, DUMP_BRIDGES, + description = "Dump generated bridges") } class JSInteropArguments(argParser: ArgParser = ArgParser("jsinterop", diff --git a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/Plugins.kt b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/Plugins.kt new file mode 100644 index 00000000000..b65ab429473 --- /dev/null +++ b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/Plugins.kt @@ -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) +} diff --git a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt index 74f951bd5d7..67cc3bc1781 100644 --- a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt +++ b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt @@ -102,6 +102,7 @@ private fun List?.isTrue(): Boolean { } private fun runCmd(command: Array, 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) : 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 = mutableListOf().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") diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CStubsManager.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CStubsManager.kt index 589fd0ff31f..ef217806c34 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CStubsManager.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CStubsManager.kt @@ -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) { + fun addStub(kotlinLocation: CompilerMessageLocation?, lines: List, 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 { + if (languageToStubs.isEmpty()) return emptyList() - val compilerOptions = mutableListOf() - 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() + 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, result: Command.Result, messageCollector: MessageCollector, verbose: Boolean @@ -88,7 +106,7 @@ class CStubsManager(private val target: KonanTarget) { throw KonanCompilationException() } - private val stubs = mutableListOf() + private val languageToStubs = mutableMapOf>() private class Stub(val kotlinLocation: CompilerMessageLocation?, val lines: List) private var counter = 0 } \ No newline at end of file diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CompilerOutput.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CompilerOutput.kt index 5e124b7e4d6..4e85a3ecb68 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CompilerOutput.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CompilerOutput.kt @@ -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) } } diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt index e7af77b2aca..3a060402d65 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt @@ -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) = diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/RuntimeNames.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/RuntimeNames.kt index c4f3f7caf33..ddc05980381 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/RuntimeNames.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/RuntimeNames.kt @@ -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") diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGen.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGen.kt index 9aa4eb478af..224ebe18500 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGen.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGen.kt @@ -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) 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 = 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, 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() - 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) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGenUtils.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGenUtils.kt index 82ca30abe31..fe3a2d93725 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGenUtils.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGenUtils.kt @@ -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() } diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/InteropIrUtils.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/InteropIrUtils.kt index f25cc253fa6..1303ed18b2e 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/InteropIrUtils.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/InteropIrUtils.kt @@ -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))) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt index 290079da243..13734173821 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt @@ -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") diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/interop/DescriptorToIrTranslationUtils.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/interop/DescriptorToIrTranslationUtils.kt index 389b580b1d8..838cd75be78 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/interop/DescriptorToIrTranslationUtils.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/interop/DescriptorToIrTranslationUtils.kt @@ -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") } } } diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/interop/IrProviderForCEnumAndCStructStubs.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/interop/IrProviderForCEnumAndCStructStubs.kt index a6911a068b6..7105b0592a5 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/interop/IrProviderForCEnumAndCStructStubs.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/interop/IrProviderForCEnumAndCStructStubs.kt @@ -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 diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/interop/cstruct/CStructVarClassGenerator.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/interop/cstruct/CStructVarClassGenerator.kt index bedb27332f4..620c555b4e6 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/interop/cstruct/CStructVarClassGenerator.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/interop/cstruct/CStructVarClassGenerator.kt @@ -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() - .filter { it.kind != CallableMemberDescriptor.Kind.FAKE_OVERRIDE } - .map(this::createProperty) - .forEach(irClass::addMember) + .getContributedDescriptors() + .filterIsInstance() + .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. + } + } + } + } } \ No newline at end of file diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/interop/cstruct/CStructVarCompanionGenerator.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/interop/cstruct/CStructVarCompanionGenerator.kt index 95d1a9defd9..8e505d85d24 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/interop/cstruct/CStructVarCompanionGenerator.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/interop/cstruct/CStructVarCompanionGenerator.kt @@ -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("size")!! val align = annotation.getArgumentValueOrNull("align")!! companionIrClass.addMember(createCompanionConstructor(companionIrClass.descriptor, size, align)) + + companionIrClass.descriptor.unsubstitutedMemberScope + .getContributedDescriptors() + .filterIsInstance() + .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 { diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/Autoboxing.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/Autoboxing.kt index ced834b7077..3327b36a81d 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/Autoboxing.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/Autoboxing.kt @@ -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) } diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt index e60b9a7df81..384216e9c2c 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt @@ -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) { - 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() + .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) } diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/SpecialBackendChecksTraversal.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/SpecialBackendChecksTraversal.kt index 195b0ce988b..67018a830a4 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/SpecialBackendChecksTraversal.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/SpecialBackendChecksTraversal.kt @@ -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") } diff --git a/kotlin-native/backend.native/tests/build.gradle b/kotlin-native/backend.native/tests/build.gradle index 6f3ba365a96..79f931ba954 100644 --- a/kotlin-native/backend.native/tests/build.gradle +++ b/kotlin-native/backend.native/tests/build.gradle @@ -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) { diff --git a/kotlin-native/backend.native/tests/interop/cpp/cppClass.kt b/kotlin-native/backend.native/tests/interop/cpp/cppClass.kt index 4b7ff5bda41..ff5c014bb8d 100644 --- a/kotlin-native/backend.native/tests/interop/cpp/cppClass.kt +++ b/kotlin-native/backend.native/tests/interop/cpp/cppClass.kt @@ -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(retByValue(null).rawValue) diff --git a/kotlin-native/backend.native/tests/interop/cpp/skia.def b/kotlin-native/backend.native/tests/interop/cpp/skia.def new file mode 100644 index 00000000000..a1d02dc8c01 --- /dev/null +++ b/kotlin-native/backend.native/tests/interop/cpp/skia.def @@ -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 class sk_sp { +public: + sk_sp(T* obj) : data(obj) {} + T* release() { + return data; + } +private: + T* data; +}; + +template sk_sp sk_ref_sp(T* obj) { + return sk_sp(obj); +} + +class Value { +public: + int data; +}; + +class Foo { +public: + Foo() { } + + virtual sk_sp foo(Value *obj) { + return sk_sp(obj); + } + + virtual Value* bar(sk_sp obj) { + return obj.release(); + } +}; diff --git a/kotlin-native/backend.native/tests/interop/cpp/skia.kt b/kotlin-native/backend.native/tests/interop/cpp/skia.kt new file mode 100644 index 00000000000..3892076e17d --- /dev/null +++ b/kotlin-native/backend.native/tests/interop/cpp/skia.kt @@ -0,0 +1,13 @@ +import kotlinx.cinterop.* +import kotlin.test.* + +import skia.* + +fun main() { + val f = Foo() + val a = nativeHeap.alloc() + a.data = 17 + val x = f.foo(a.ptr) + val z = f.bar(x) + println("${a?.data} ${z?.pointed?.data}") +} diff --git a/kotlin-native/backend.native/tests/interop/cpp/types.kt b/kotlin-native/backend.native/tests/interop/cpp/types.kt index e846085f1d0..348144e94f0 100644 --- a/kotlin-native/backend.native/tests/interop/cpp/types.kt +++ b/kotlin-native/backend.native/tests/interop/cpp/types.kt @@ -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(retByRefConst(k).rawValue) assertEquals(k, x.get()) } - +/* @Test fun test_paramByValue(k: Int) { val x = nativeHeap.alloc() {} @@ -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() {} @@ -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()) diff --git a/kotlin-native/build.gradle b/kotlin-native/build.gradle index d3a7da75250..083018298d6 100644 --- a/kotlin-native/build.gradle +++ b/kotlin-native/build.gradle @@ -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') diff --git a/kotlin-native/klib/src/main/kotlin/org/jetbrains/kotlin/cli/klib/DefaultDeclarationHeaderRenderer.kt b/kotlin-native/klib/src/main/kotlin/org/jetbrains/kotlin/cli/klib/DefaultDeclarationHeaderRenderer.kt index efd252eec42..b18efdc9c07 100644 --- a/kotlin-native/klib/src/main/kotlin/org/jetbrains/kotlin/cli/klib/DefaultDeclarationHeaderRenderer.kt +++ b/kotlin-native/klib/src/main/kotlin/org/jetbrains/kotlin/cli/klib/DefaultDeclarationHeaderRenderer.kt @@ -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 diff --git a/kotlin-native/samples/cpptest/NOTES-ru.md b/kotlin-native/samples/cpptest/NOTES-ru.md deleted file mode 100644 index e49d09e3308..00000000000 --- a/kotlin-native/samples/cpptest/NOTES-ru.md +++ /dev/null @@ -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. (Никто и не пользуется: даже 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(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): 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 компилятор и так сделает копию. diff --git a/kotlin-native/samples/cpptest/ReadMe.md b/kotlin-native/samples/cpptest/ReadMe.md deleted file mode 100644 index 00db8aad954..00000000000 --- a/kotlin-native/samples/cpptest/ReadMe.md +++ /dev/null @@ -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 - diff --git a/kotlin-native/samples/cpptest/features.def b/kotlin-native/samples/cpptest/features.def deleted file mode 100644 index 3ebfc6b8aff..00000000000 --- a/kotlin-native/samples/cpptest/features.def +++ /dev/null @@ -1,6 +0,0 @@ -headers = src/native/features.h -headerFilter = src/native/features.h -package = test -language = C++ -#compilerOpts = -std=c++14 - diff --git a/kotlin-native/samples/cpptest/src/kotlin/FeatureTest/main.kt b/kotlin-native/samples/cpptest/src/kotlin/FeatureTest/main.kt deleted file mode 100644 index dab46b864f9..00000000000 --- a/kotlin-native/samples/cpptest/src/kotlin/FeatureTest/main.kt +++ /dev/null @@ -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.__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.__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.__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.__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() - var i = alloc() - 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__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() { - memcpy(ptr, ns__create(), typeOf().size.convert()) // use placement new here - } - cxx.foo(null) - nativeHeap.free(cxx) -} -/* -fun testCtor1() { - println("testCtor1: interpretPointed(ns__CppTest.__create__().rawValue)") - val theStruct = interpretPointed(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__bar(null).rawValue) -// theS.foo(null) - - println("x.useContents {iPub} = ${x.useContents {iPub}}" ) -} diff --git a/kotlin-native/samples/cpptest/src/native/features.cpp b/kotlin-native/samples/cpptest/src/native/features.cpp deleted file mode 100644 index 10d58acd528..00000000000 --- a/kotlin-native/samples/cpptest/src/native/features.cpp +++ /dev/null @@ -1,73 +0,0 @@ -#include "features.h" - -#include - -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(); -} \ No newline at end of file diff --git a/kotlin-native/samples/cpptest/src/native/features.h b/kotlin-native/samples/cpptest/src/native/features.h deleted file mode 100644 index da8f8df5fe4..00000000000 --- a/kotlin-native/samples/cpptest/src/native/features.h +++ /dev/null @@ -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 struct TmplStruct { -public: - void baz() const {} -}; - -void funTmplParam(TmplStruct& t); - -template void tmplFunction(); -template void tmplFunction(X x); - -//int funVariadic(const char* format, ...); - -//#include -//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 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 class TmplClass { -public: - void baz() const {} -}; - -typedef class TmplStruct IntTmplStruct; -IntTmplStruct intTmplStruct; - -struct Smth { - IntTmplStruct intTmplStruct; -}; -*/ diff --git a/kotlin-native/samples/skia/README.md b/kotlin-native/samples/skia/README.md deleted file mode 100644 index f645c154629..00000000000 --- a/kotlin-native/samples/skia/README.md +++ /dev/null @@ -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" diff --git a/kotlin-native/samples/skia/Skia.def b/kotlin-native/samples/skia/Skia.def deleted file mode 100644 index 328418b951b..00000000000 --- a/kotlin-native/samples/skia/Skia.def +++ /dev/null @@ -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++ diff --git a/kotlin-native/samples/skia/main.cpp b/kotlin-native/samples/skia/main.cpp deleted file mode 100644 index 2b1be6b3427..00000000000 --- a/kotlin-native/samples/skia/main.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "include/core/SkTime.h" -#include - -using namespace std; - -int main() -{ - auto nsec = SkTime::GetNSecs(); - cout << nsec << endl; - return 0; -} \ No newline at end of file diff --git a/kotlin-native/samples/skia/src/skiaMain/kotlin/sktimeSample.kt b/kotlin-native/samples/skia/src/skiaMain/kotlin/sktimeSample.kt deleted file mode 100644 index 3bf8b683cc8..00000000000 --- a/kotlin-native/samples/skia/src/skiaMain/kotlin/sktimeSample.kt +++ /dev/null @@ -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.GetDateTime(dateTime.ptr) - println("${dateTime.fYear}-${dateTime.fMonth}-${dateTime.fDay} ${dateTime.fHour}:${dateTime.fMinute}.${dateTime.fSecond}" ) - } - -} \ No newline at end of file diff --git a/kotlin-native/samples/skia/src/skiaMain/kotlin/test_SkString.kt b/kotlin-native/samples/skia/src/skiaMain/kotlin/test_SkString.kt deleted file mode 100644 index 9c3dfed0ba9..00000000000 --- a/kotlin-native/samples/skia/src/skiaMain/kotlin/test_SkString.kt +++ /dev/null @@ -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.__init__(ptr, cString) - } - println(s.c_str()?.toKString()) - } -} - -fun SkString_create(from: String): SkString { - val res = nativeHeap.alloc() {} - memScoped { - val cString = from.cstr.getPointer(memScope) - SkString.__init__(res.ptr, cString) - } - return res -} - -fun SkString.clone(): SkString { - return nativeHeap.alloc() { - 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(), "back!".cstr.getPointer(memScope)) - goback.resize((pos + "back!".length).convert()) - } - 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() -} \ No newline at end of file diff --git a/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/util/DefFile.kt b/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/util/DefFile.kt index b0a76b3f8f3..5bca0601399 100644 --- a/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/util/DefFile.kt +++ b/kotlin-native/shared/src/main/kotlin/org/jetbrains/kotlin/konan/util/DefFile.kt @@ -118,6 +118,10 @@ class DefFile(val file:File?, val config:DefFileConfig, val manifestAddendProper properties.getProperty("foreignExceptionMode") } + val pluginName by lazy { + properties.getProperty("plugin") + } + } } diff --git a/settings.gradle b/settings.gradle index d6937ef2372..fa6dfb26513 100644 --- a/settings.gradle +++ b/settings.gradle @@ -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'