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

This commit is contained in:
Alexander Gorshenev
2020-12-21 03:11:45 +03:00
parent 5f582ad28a
commit 887032667d
62 changed files with 1160 additions and 1155 deletions
+56
View File
@@ -0,0 +1,56 @@
Skia interop support
====================
This is a plugin for cinterop that allows to interop with `Skia Graphics Library`.
Primarily targeted to be used by Skiko project.
Usage:
------
Add to the .def file the following clauses:
```
plugin = org.jetbrains.kotlin.native.cinterop.plugin.skia
language = C++
```
Implementation details
======================
Limited C++ interop provided via plain C wrappers and cinterop mechanism.
WARNING: this is by no means a general support for C++ by cinterop.
C++ features
------------
* C++ class:
+ Virtual and non-virtual methods
* Static methods mapped as companion ones
* Fields
* Static fields as companion ones
* Constructors are mapped to `__init__(args)` companion methods
* Destructor is mapped to `__destroy__(this)` companion method
* LValueReference parameters and return value internally handled as pointers
* Namespaces provided as simple mangled class name. It works but awfully ugly. Shall be fixed by mapping to packages.
* Nested C++ classes: same as namespaces, simple mangling. Shall be fixed.
* Access modifiers: only public members exposed to Kotlin. Anonymous namespaces are silently ignored.
Known issues
------------
* C++ object with nontrivial copy constructor may work incorrect when used as by value parameter or return typr.
"Nontrivial" in this context relates to objects which bahavior depends on memory location. Most of other non-POD objects may be handled well.
In fact, "by value" return type is mapped to CValue which is immutrable movable block. Particularly, non-const methods invoked with CValue receiver
via useContents mechanism run on temporary object and therefore does not modify the original copy. TBD.
Limitations
-----------
* Operators are not supported yet (silently ignored)
* LVReference is mapped to CPointer<T>? which is incorrect (should be notNull). This may cause segmentation fault in case of null would be sent as a parameter. TBD
* const overload not supported and cause compilation error. That is, two class methods with the same signature (`const` and `non-const`) can't be compiled. The same for function parameters: if two functions differ only in `const*` modifier of parameter, this will cause "conflicting overloads" error. TBD.
* C++ lambda type is not supported yet.
* Member pointer, member reference, rvalue reference and some other types are not wupported.
* Inheritance is not implemented yet. C++-style callbacks (overriding virtual method in Kotlin) may be implemented via plain C bridge (this can be done by hand as a workaround). TBD.
+32
View File
@@ -0,0 +1,32 @@
/*
* Copyright 2010-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
buildscript {
apply from: "$rootDir/kotlin-native/gradle/kotlinGradlePlugin.gradle"
}
apply plugin: 'kotlin'
dependencies {
implementation project(":kotlin-native:Interop:Indexer")
implementation project(":kotlin-native:Interop:StubGenerator")
}
compileKotlin {
kotlinOptions {
allWarningsAsErrors=true
}
}
@@ -0,0 +1,81 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.native.interop.skia
import clang.*
import kotlinx.cinterop.CValue
import org.jetbrains.kotlin.native.interop.indexer.*
fun buildSkiaNativeIndexImpl(library: NativeLibrary, verbose: Boolean): IndexerResult {
val result = SkiaNativeIndexImpl(library, verbose)
return buildNativeIndexImpl(result)
}
class SkiaNativeIndexImpl(library: NativeLibrary, verbose: Boolean) : NativeIndexImpl(library, verbose) {
override fun convertType(type: CValue<CXType>, typeAttributes: CValue<CXTypeAttributes>?): Type {
if (type.kind == CXTypeKind.CXType_Record) {
val decl: StructDecl = getStructDeclAt(clang_getTypeDeclaration(type))
if (decl.isSkiaSharedPointer) {
return ManagedType(decl)
}
}
return super.convertType(type, typeAttributes)
}
// Skip functions which parameter or return type is TemplateRef
override fun isFuncDeclEligible(cursor: CValue<CXCursor>): Boolean =
cursor.containsOnlySkiaSharedPointerTemplates()
override fun String.isUnknownTemplate() = // TODO: this is a hack.
this.isCppTemplate && !this.isSkiaSharedPointer
}
fun CValue<CXCursor>.containsTemplates(): Boolean {
var ret = false
visitChildren(this) { childCursor, _ ->
when (childCursor.kind) {
CXCursorKind.CXCursor_TemplateRef -> {
ret = true
CXChildVisitResult.CXChildVisit_Break
}
else -> CXChildVisitResult.CXChildVisit_Recurse
}
}
return ret
}
fun CValue<CXCursor>.containsOnlySkiaSharedPointerTemplates(): Boolean {
var ret = true
visitChildren(this) { childCursor, _ ->
when (childCursor.kind) {
CXCursorKind.CXCursor_TemplateRef ->
if (childCursor.spelling == "sk_sp" && !childCursor.containsTemplates()) {
CXChildVisitResult.CXChildVisit_Continue
} else {
ret = false
CXChildVisitResult.CXChildVisit_Break
}
else -> CXChildVisitResult.CXChildVisit_Recurse
}
}
return ret
}
val StructDecl.isSkiaSharedPointer: Boolean
get() = spelling.isSkiaSharedPointer
val StructDecl.stripSkiaSharedPointer: String
get() {
assert(this.isSkiaSharedPointer)
return this.spelling.drop(6).dropLast(1).let { // TODO: this is a hack.
if (it.startsWith("const ")) it.drop(6) else it
}
}
private val String.isCppTemplate: Boolean // TODO: this is a hack.
get() = this.contains("<") && this.endsWith(">")
private val String.isSkiaSharedPointer: Boolean // TODO: this is a hack.
get() = this.startsWith("sk_sp<") && this.endsWith(">")
@@ -0,0 +1,32 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.native.interop.skia
import org.jetbrains.kotlin.native.interop.gen.ManagedTypePassing
import org.jetbrains.kotlin.native.interop.gen.StubIrContext
import org.jetbrains.kotlin.native.interop.gen.getStringRepresentation
import org.jetbrains.kotlin.native.interop.gen.jvm.Plugin
import org.jetbrains.kotlin.native.interop.indexer.IndexerResult
import org.jetbrains.kotlin.native.interop.indexer.ManagedType
import org.jetbrains.kotlin.native.interop.indexer.NativeLibrary
import org.jetbrains.kotlin.native.interop.indexer.Type
class SkiaPlugin : Plugin {
override val name = "Skia"
override fun buildNativeIndex(library: NativeLibrary, verbose: Boolean): IndexerResult =
buildSkiaNativeIndexImpl(library, verbose)
override val managedTypePassing = object : ManagedTypePassing() {
override val ManagedType.passValue: String get() = "sk_ref_sp<${this.decl.stripSkiaSharedPointer}>"
override val ManagedType.returnValue: String get() = ".release()"
}
override val ManagedType.stringRepresentation: String get() {
assert(this.decl.isSkiaSharedPointer)
return "${this.decl.stripSkiaSharedPointer}*"
}
override fun stubsBuildingContext(stubIrContext: StubIrContext) = SkiaStubsBuildingContextImpl(stubIrContext)
}
@@ -0,0 +1,26 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.native.interop.skia
import org.jetbrains.kotlin.native.interop.gen.Classifier
import org.jetbrains.kotlin.native.interop.gen.StubIrContext
import org.jetbrains.kotlin.native.interop.gen.StubsBuildingContextImpl
import org.jetbrains.kotlin.native.interop.indexer.StructDecl
class SkiaStubsBuildingContextImpl(stubIrContext: StubIrContext) : StubsBuildingContextImpl(stubIrContext) {
override val declarationMapper = SkiaDeclarationMapperImpl()
inner class SkiaDeclarationMapperImpl : DeclarationMapperImpl() {
override fun getKotlinClassForManaged(structDecl: StructDecl): Classifier {
assert(structDecl.isSkiaSharedPointer)
val struct = structDecl.stripSkiaSharedPointer
val structArgument = nativeIndex.structs.singleOrNull {
it.spelling == struct && it.def != null
} ?: error("Expected to find a single template arg struct by name: ${struct}")
return getKotlinClassForPointed(structArgument)
}
}
}