Garbage collection capable wrappers for skia interop

This commit is contained in:
Alexander Gorshenev
2021-05-06 19:11:05 +03:00
parent d9483ccb08
commit 9148094bbd
30 changed files with 1114 additions and 121 deletions
@@ -0,0 +1,15 @@
/*
* 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 kotlinx.cinterop
import kotlinx.cinterop.CStructVar
interface SkiaRefCnt
interface CPlusPlusClass
abstract class ManagedType<T : CStructVar>(val cpp: T)
val <T : CStructVar> ManagedType<T>.ptr: CPointer<T> get() = this.cpp.ptr
@@ -24,6 +24,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 CPlusPlusClass
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
annotation class ManagedType
+28
View File
@@ -1,3 +1,4 @@
Skia interop support
====================
@@ -15,6 +16,33 @@ plugin = org.jetbrains.kotlin.native.cinterop.plugin.skia
language = C++
```
Interop
-------
There are two kinds of C++ classes used in Skia.
The ones with ref()/unref() reference counting are abstracted with SkiaRefCnt interface.
The ones without are abstracted with CPlusPlusClass interface.
So, for example, for SkPaint one has
class SkPaint(rawPtr) : CStructVar(rawPtr), CPlusPlusClass {
// A C interop-like function having methods,
// as well as low level __init__ and __destroy__
}
class Paint(cpp: SkPaint, managed: Boolean) : ManagedType(cpp) {
// A wrapper capable of garbage collection delegating all calls and fields to SkPaint
}
* pointer returned from C++ -> Wrapper(cpp, managed = false)
* sk_sp returned from C++ -> calls .release() ; Wrapper(cpp, managed = true)
* pointer passed to C++ -> kotlin.cpp.ptr
* sk_sp passed to C++ -> sk_ref_sp(kotlin.cpp.ptr)
* constructor call -> allocate Cpp; Wrapper(cpp, managed = true)
* garbage collection -> if (managed==true): calls __destroy__() for CPluaPlusClass or unref() for SkiaRefCnt
Implementation details
======================
@@ -9,6 +9,7 @@ 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
import org.jetbrains.kotlin.native.interop.indexer.StructDef
class SkiaStubsBuildingContextImpl(stubIrContext: StubIrContext) : StubsBuildingContextImpl(stubIrContext) {
override val declarationMapper = SkiaDeclarationMapperImpl()
@@ -23,4 +24,21 @@ class SkiaStubsBuildingContextImpl(stubIrContext: StubIrContext) : StubsBuilding
return getKotlinClassForPointed(structArgument)
}
}
override fun isCppClass(spelling: String): Boolean {
val decl = nativeIndex.structs.firstOrNull { it.spelling == spelling } ?: return false
return decl.def?.kind == StructDef.Kind.CLASS
}
override fun managedWrapperClassifier(cppClassifier: Classifier): Classifier? {
if (cppClassifier.pkg != "org.jetbrains.skiko.skia.native") return null
// TODO: it'd be nice to check inheritance from SkiaRefCnt or CPlusPlusClass,
// but unable to do that at StubType level.
if (!(cppClassifier.topLevelName.startsWith("Sk") || cppClassifier.topLevelName.startsWith("Gr"))) return null
if (cppClassifier.topLevelName == "SkString") return null
// TODO: We only managed C++ classes, not structs for now.
if (!isCppClass(cppClassifier.topLevelName)) return null
return Classifier.topLevel(cppClassifier.pkg, cppClassifier.topLevelName.drop(2))
}
}
@@ -0,0 +1,132 @@
/*
* 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
class DeepCopyForManagedWrapper(val originalClass: ClassStub, val context: StubsBuildingContext) : StubIrTransformer {
override fun visitClass(element: ClassStub): ClassStub = error("Not implemented")
override fun visitTypealias(element: TypealiasStub): TypealiasStub = error("Not implemented")
override fun visitFunction(element: FunctionStub): FunctionStub {
return FunctionStub(
name = element.name,
returnType = transformCPointerToManaged(element.returnType),
parameters = element.parameters.map { visitFunctionParameter(it) },
origin = element.origin,
annotations = emptyList(),
external = false,
receiver = element.receiver?.let { visitReceiverParameter(it) },
modality = element.modality,
typeParameters = element.typeParameters,
isOverride = element.isOverride,
hasStableParameterNames = element.hasStableParameterNames
)
}
override fun visitProperty(element: PropertyStub): PropertyStub {
return PropertyStub(
name = element.name,
type = element.type,
origin = element.origin,
annotations = emptyList(),
receiverType = element.receiverType,
modality = element.modality,
isOverride = element.isOverride,
kind = when(element.kind) {
is PropertyStub.Kind.Val -> PropertyStub.Kind.Val(
visitPropertyAccessor(element.kind.getter) as PropertyAccessor.Getter
)
is PropertyStub.Kind.Var -> PropertyStub.Kind.Var(
visitPropertyAccessor(element.kind.getter) as PropertyAccessor.Getter,
visitPropertyAccessor(element.kind.setter) as PropertyAccessor.Setter
)
is PropertyStub.Kind.Constant -> PropertyStub.Kind.Constant(element.kind.constant)
}
)
}
override fun visitConstructor(constructorStub: ConstructorStub): ConstructorStub {
return if (constructorStub.isPrimary)
ConstructorStub(
parameters = listOf(
FunctionParameterStub(
name = "cpp",
type = ClassifierStubType(originalClass.classifier)
),
FunctionParameterStub(
name = "managed",
type = ClassifierStubType(Classifier.topLevel("kotlin", "Boolean"))
)
),
isPrimary = true,
visibility = constructorStub.visibility,
origin = constructorStub.origin
)
else ConstructorStub(
parameters = constructorStub.parameters.map { visitFunctionParameter(it) },
annotations = emptyList(),
isPrimary = constructorStub.isPrimary,
visibility = constructorStub.visibility,
origin = constructorStub.origin
)
}
override fun visitPropertyAccessor(propertyAccessor: PropertyAccessor): PropertyAccessor {
return when (propertyAccessor) {
is PropertyAccessor.Getter.SimpleGetter ->
PropertyAccessor.Getter.SimpleGetter(
propertyAccessor.annotations.map { visitAnnotation(it) },
constant = propertyAccessor.constant
)
is PropertyAccessor.Getter.ExternalGetter ->
PropertyAccessor.Getter.SimpleGetter( // TODO: is it right?
propertyAccessor.annotations.map { visitAnnotation(it) },
constant = null
)
is PropertyAccessor.Setter.SimpleSetter ->
PropertyAccessor.Setter.SimpleSetter(
propertyAccessor.annotations.map { visitAnnotation(it) }
)
is PropertyAccessor.Setter.ExternalSetter ->
PropertyAccessor.Setter.SimpleSetter( // TODO: is it right?
propertyAccessor.annotations.map { visitAnnotation(it) }
)
else -> error("Not implemented yet $propertyAccessor")
}
}
override fun visitSimpleStubContainer(simpleStubContainer: SimpleStubContainer): SimpleStubContainer = SimpleStubContainer()
private fun transformCPointerToManaged(type: StubType): StubType {
if (type !is ClassifierStubType) return type
if (type.classifier.topLevelName != "CPointer" && type.classifier.topLevelName != "CValuesRef") return type
if (type.classifier.pkg != "kotlinx.cinterop") return type
val argument = type.typeArguments.single() as? TypeArgumentStub ?: return type
if (argument.type !is ClassifierStubType) return type
val newClassifier = managedWrapperClassifier(argument.type.classifier) ?: return type
return ClassifierStubType(newClassifier, nullable = type.nullable)
}
fun managedWrapperClassifier(cppClassifier: Classifier): Classifier? =
(context as StubsBuildingContextImpl).managedWrapperClassifier(cppClassifier)
override fun visitFunctionParameter(element: FunctionParameterStub): FunctionParameterStub {
return FunctionParameterStub(
name = element.name,
type = transformCPointerToManaged(element.type),
annotations = element.annotations.map { visitAnnotation(it) },
isVararg = element.isVararg
)
}
override fun visitReceiverParameter(element: ReceiverParameterStub): ReceiverParameterStub {
return ReceiverParameterStub(
type = element.type
)
}
override fun visitAnnotation(element: AnnotationStub): AnnotationStub = element
}
@@ -133,7 +133,7 @@ sealed class TypeMirror(val pointedType: KotlinClassifierType, val info: TypeInf
) : TypeMirror(pointedType, info) {
override val argType: KotlinType
get() = pointedType
get() = KotlinTypes.cPointer.typeWith(pointedType)
}
}
@@ -113,6 +113,11 @@ sealed class StubOrigin {
* E.CEnumVar.value.
*/
class EnumVarValueField(val enum: EnumDef) : Synthetic()
/**
* Other synthetic values.
*/
object ManagedTypeDetails : StubOrigin()
}
class ObjCCategoryInitMethod(
@@ -210,6 +215,8 @@ sealed class AnnotationStub(val classifier: Classifier) {
class VarType(val size: Long, val align: Int) : AnnotationStub(cStructClassifier.nested("VarType"))
object ManagedType : AnnotationStub(cStructClassifier.nested("ManagedType"))
object CPlusPlusClass : AnnotationStub(cStructClassifier.nested("CPlusPlusClass"))
}
class CNaturalStruct(val members: List<StructMember>) :
@@ -324,11 +331,11 @@ sealed class ClassStub : StubContainer(), StubElementWithOrigin, AnnotationHolde
abstract val companion : Companion?
abstract val classifier: Classifier
class Simple(
open class Simple(
override val classifier: Classifier,
val modality: ClassStubModality,
constructors: List<ConstructorStub> = emptyList(),
methods: List<FunctionStub> = emptyList(),
val constructors: List<ConstructorStub> = emptyList(),
val methods: List<FunctionStub> = emptyList(),
override val superClassInit: SuperClassInit? = null,
override val interfaces: List<StubType> = emptyList(),
override val properties: List<PropertyStub> = emptyList(),
@@ -343,7 +350,7 @@ sealed class ClassStub : StubContainer(), StubElementWithOrigin, AnnotationHolde
class Companion(
override val classifier: Classifier,
methods: List<FunctionStub> = emptyList(),
val methods: List<FunctionStub> = emptyList(),
override val superClassInit: SuperClassInit? = null,
override val interfaces: List<StubType> = emptyList(),
override val properties: List<PropertyStub> = emptyList(),
@@ -246,6 +246,12 @@ open class StubsBuildingContextImpl(
return classifier
}
open fun isCppClass(spelling: String): Boolean =
error("Only meaningful with a proper cpp plugin")
open fun managedWrapperClassifier(cppClassifier: Classifier): Classifier? =
error("Only meaningful with a proper cpp plugin")
open inner class DeclarationMapperImpl : DeclarationMapper {
override fun getKotlinClassForPointed(structDecl: StructDecl): Classifier {
val baseName = structDecl.kotlinName
@@ -277,7 +283,6 @@ open class StubsBuildingContextImpl(
KotlinPlatform.NATIVE -> true
}
}
}
data class StubIrBuilderResult(
@@ -78,16 +78,30 @@ internal class StructStubBuilder(
AnnotationStub.CStruct(it)
}
}
val managedAnnotation = if (context.configuration.library.language == Language.CPP
val cPlusPlusClassAnnotation = if (context.configuration.library.language == Language.CPP
&& def.kind == StructDef.Kind.CLASS) {
AnnotationStub.CStruct.ManagedType
AnnotationStub.CStruct.CPlusPlusClass
} else {
null
}
val structAnnotations = listOfNotNull(structAnnotation, managedAnnotation)
val structAnnotations = listOfNotNull(structAnnotation, cPlusPlusClassAnnotation)
val classifier = context.getKotlinClassForPointed(decl)
val destructor = if (def.methods.any { it.isCxxDestructor && it.name == "__destroy__" }) {
listOf(
FunctionStub(
name = "__destroy__",
returnType = ClassifierStubType(Classifier("kotlin", "Unit")),
parameters = emptyList(),
origin = StubOrigin.Synthetic.ManagedTypeDetails,
annotations = emptyList(),
receiver = null, // ReceiverParameterStub()
modality = MemberStubModality.FINAL
)
)
} else emptyList()
val methods: List<FunctionStub> =
def.methods
.filter { it.isCxxInstanceMethod }
@@ -100,7 +114,7 @@ internal class StructStubBuilder(
} catch (e: Throwable) {
null
}
}.filterNotNull()
}.filterNotNull() + destructor
val fields: List<PropertyStub?> = def.fields.map { field ->
try {
@@ -214,17 +228,19 @@ internal class StructStubBuilder(
}.filterNotNull()
// Here's what we have for C++.
// Note that we account for constructors twice.
// Note that we account for constructors and the destructor 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)
// __destroy__()
//
// // These go into `methods`
// foo()
// bar(x, y)
//
// Companion {
// // These all go to `classMethods`
@@ -256,7 +272,20 @@ internal class StructStubBuilder(
properties = classFields,
methods = classMethods
)
return listOf(ClassStub.Simple(
val interfaces = if (context.configuration.library.language == Language.CPP && def.kind == StructDef.Kind.CLASS) {
listOfNotNull(
ClassifierStubType(Classifier.topLevel("kotlinx.cinterop", "CPlusPlusClass")),
// TODO: Only if skia plugin is active!
if (methods.any { it.name == "unref" }) {
ClassifierStubType(Classifier.topLevel("kotlinx.cinterop", "SkiaRefCnt"))
} else {
null
}
)
} else emptyList()
val classStub = ClassStub.Simple(
classifier,
origin = origin,
properties = fields.filterNotNull() + if (platform == KotlinPlatform.NATIVE) bitFields else emptyList(),
@@ -265,8 +294,63 @@ internal class StructStubBuilder(
modality = ClassStubModality.NONE,
annotations = structAnnotations,
superClassInit = superClassInit,
companion = companion
))
companion = companion,
interfaces = interfaces
)
return if (context.configuration.library.language == Language.CPP && def.kind == StructDef.Kind.CLASS) {
try {
listOfNotNull(classStub, buildManagedWrapper(classStub))
} catch (e: Throwable) {
emptyList()
}
} else {
listOf(classStub)
}
}
private fun buildManagedWrapper(classStub: ClassStub.Simple): ClassStub.Simple? {
val copier = DeepCopyForManagedWrapper(classStub, context)
val managedName = copier.managedWrapperClassifier(classStub.classifier) ?: run {
return null
}
val managed = PropertyStub(
name = "managed",
type = ClassifierStubType(Classifier.topLevel("kotlin", "Boolean")),
kind = PropertyStub.Kind.Val(getter = PropertyAccessor.Getter.SimpleGetter()),
modality = MemberStubModality.FINAL,
origin = StubOrigin.Synthetic.ManagedTypeDetails
)
val constructors = classStub.constructors.map { copier.visitConstructor(it) }
val managedWrapper = ClassStub.Simple(
managedName,
classStub.modality,
constructors,
classStub.methods.map { copier.visitFunction(it) } ,
superClassInit = SuperClassInit(
ClassifierStubType(
Classifier.topLevel("kotlinx.cinterop", "ManagedType"),
listOf(TypeArgumentStub(ClassifierStubType(classStub.classifier)))
),
listOf( GetConstructorParameter(constructors.single { it.isPrimary }.parameters.first()) )
),
interfaces = emptyList(),
properties = listOf(managed) + classStub.properties.map { copier.visitProperty(it) },
classStub.origin,
annotations = listOf(AnnotationStub.CStruct.ManagedType),
childrenClasses = emptyList(),
companion = ClassStub.Companion(
classifier = managedName.nested("Companion"),
methods = classStub.companion!!.methods
.filterNot { it.name == "__init__" || it.name == "__destroy__" }
.map { copier.visitFunction(it) },
)
)
return managedWrapper
}
private fun getArrayLength(type: ArrayType): Long {
@@ -447,6 +447,7 @@ private class MappingExtensions(
("size" to KmAnnotationArgument.LongValue(size)),
("align" to KmAnnotationArgument.IntValue(align))
)
is AnnotationStub.CStruct.CPlusPlusClass -> emptyMap()
is AnnotationStub.CStruct.ManagedType -> emptyMap()
}
return KmAnnotation(classifier.fqNameSerialized, args)
@@ -490,6 +490,8 @@ class StubIrTextEmitter(
"@CStruct(${annotationStub.struct.quoteAsKotlinLiteral()})"
is AnnotationStub.CNaturalStruct ->
"@CNaturalStruct(${annotationStub.members.joinToString { it.name.quoteAsKotlinLiteral() }})"
is AnnotationStub.CStruct.CPlusPlusClass ->
"@CStruct.CPlusPlusClass"
is AnnotationStub.CStruct.ManagedType ->
"@CStruct.ManagedType"
is AnnotationStub.CLength ->
@@ -19,4 +19,28 @@ interface StubIrVisitor<T, R> {
fun visitPropertyAccessor(propertyAccessor: PropertyAccessor, data: T): R
fun visitSimpleStubContainer(simpleStubContainer: SimpleStubContainer, data: T): R
}
}
interface StubIrTransformer {
fun visitClass(element: ClassStub): ClassStub
fun visitTypealias(element: TypealiasStub): TypealiasStub
fun visitFunction(element: FunctionStub): FunctionStub
fun visitProperty(element: PropertyStub): PropertyStub
fun visitConstructor(constructorStub: ConstructorStub): ConstructorStub
fun visitPropertyAccessor(propertyAccessor: PropertyAccessor): PropertyAccessor
fun visitSimpleStubContainer(simpleStubContainer: SimpleStubContainer): SimpleStubContainer
fun visitFunctionParameter(element: FunctionParameterStub): FunctionParameterStub
fun visitAnnotation(element: AnnotationStub): AnnotationStub
fun visitReceiverParameter(element: ReceiverParameterStub): ReceiverParameterStub
}
@@ -118,6 +118,10 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) {
val CreateNSStringFromKString = packageScope.getContributedFunctions("CreateNSStringFromKString").single()
val nativeHeap = packageScope.getContributedClass("nativeHeap")
val cPointed = packageScope.getContributedClass("CPointed")
val managedType = packageScope.getContributedClass("ManagedType")
val cPlusPlusClass = packageScope.getContributedClass("CPlusPlusClass")
val skiaRefCnt = packageScope.getContributedClass("SkiaRefCnt")
val interopGetPtr = packageScope.getContributedVariables("ptr").single {
val singleTypeParameter = it.typeParameters.singleOrNull()
val singleTypeParameterUpperBound = singleTypeParameter?.upperBounds?.singleOrNull()
@@ -128,6 +132,17 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) {
TypeUtils.getClassDescriptor(singleTypeParameterUpperBound) == cPointed &&
extensionReceiverParameter.type == singleTypeParameter.defaultType
}.getter!!
val interopManagedGetPtr = 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) == cStructVar &&
TypeUtils.getClassDescriptor(extensionReceiverParameter.type) == managedType
}.getter!!
}
private fun MemberScope.getContributedVariables(name: String) =
@@ -14,7 +14,9 @@ object RuntimeNames {
val cStructArrayMemberAt = FqName("kotlinx.cinterop.internal.CStruct.ArrayMemberAt")
val cStructBitField = FqName("kotlinx.cinterop.internal.CStruct.BitField")
val cStruct = FqName("kotlinx.cinterop.internal.CStruct")
val cppClass = FqName("kotlinx.cinterop.internal.CStruct.CPlusPlusClass")
val managedType = FqName("kotlinx.cinterop.internal.CStruct.ManagedType")
val skiaRefCnt = FqName("kotlinx.cinterop.SkiaRefCnt") // TODO: move me to the plugin?
val objCMethodAnnotation = FqName("kotlinx.cinterop.ObjCMethod")
val objCMethodImp = FqName("kotlinx.cinterop.ObjCMethodImp")
val independent = FqName("kotlin.native.internal.Independent")
@@ -22,4 +24,5 @@ object RuntimeNames {
val kotlinNativeInternalPackageName = FqName.fromSegments(listOf("kotlin", "native", "internal"))
val associatedObjectKey = FqName("kotlin.reflect.AssociatedObjectKey")
val typedIntrinsicAnnotation = FqName("kotlin.native.internal.TypedIntrinsic")
val cleaner = FqName("kotlin.native.internal.Cleaner")
}
@@ -109,7 +109,7 @@ private fun KotlinToCCallBuilder.buildKotlinBridgeCall(transformCall: (IrMemberA
transformCall
)
private fun IrType.isManagedType(): Boolean= this.classOrNull?.owner?.hasAnnotation(RuntimeNames.managedType) ?: false
private fun IrType.isCppClass(): Boolean= this.classOrNull?.owner?.hasAnnotation(RuntimeNames.cppClass) ?: false
internal fun KotlinStubs.generateCCall(expression: IrCall, builder: IrBuilderWithScope, isInvoke: Boolean,
foreignExceptionMode: ForeignExceptionMode.Mode = ForeignExceptionMode.default): IrExpression {
@@ -183,7 +183,7 @@ internal fun KotlinStubs.generateCCall(expression: IrCall, builder: IrBuilderWit
private fun KotlinToCCallBuilder.addArguments(arguments: List<IrExpression?>, callee: IrFunction) {
arguments.forEachIndexed { index, argument ->
val parameter = if (callee.dispatchReceiverParameter != null &&
(callee.dispatchReceiverParameter?.type?.isManagedType() == true)) {
(callee.dispatchReceiverParameter?.type?.isCppClass() == true)) {
if (index == 0) callee.dispatchReceiverParameter!! else callee.valueParameters[index-1]
} else {
@@ -736,7 +736,7 @@ private fun KotlinStubs.mapType(
val cStructType = getNamedCStructType(kotlinClass)
require(cStructType != null) { renderCompilerError(location) }
if (type.isManagedType()) {
if (type.isCppClass()) {
// 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.
@@ -85,6 +85,7 @@ internal fun IrType.isObjCReferenceType(target: KonanTarget, irBuiltIns: IrBuilt
internal fun IrType.isCPointer(symbols: KonanSymbols): Boolean = this.classOrNull == symbols.interopCPointer
internal fun IrType.isCValue(symbols: KonanSymbols): Boolean = this.classOrNull == symbols.interopCValue
internal fun IrType.isCValuesRef(symbols: KonanSymbols): Boolean = this.classOrNull == symbols.interopCValuesRef
internal fun IrType.isNativePointed(symbols: KonanSymbols): Boolean = isSubtypeOfClass(symbols.nativePointed)
@@ -206,6 +206,12 @@ internal class KonanSymbols(
val interopGetPtr = symbolTable.referenceSimpleFunction(context.interopBuiltIns.interopGetPtr)
val interopManagedGetPtr = symbolTable.referenceSimpleFunction(context.interopBuiltIns.interopManagedGetPtr)
val interopManagedType = symbolTable.referenceClass(context.interopBuiltIns.managedType)
val interopCPlusPlusClass = symbolTable.referenceClass(context.interopBuiltIns.cPlusPlusClass)
val interopSkiaRefCnt = symbolTable.referenceClass(context.interopBuiltIns.skiaRefCnt)
val readBits = interopFunction("readBits")
val writeBits = interopFunction("writeBits")
@@ -6,6 +6,7 @@ package org.jetbrains.kotlin.backend.konan.ir.interop
import org.jetbrains.kotlin.backend.common.ir.createParameterDeclarations
import org.jetbrains.kotlin.backend.konan.InteropBuiltIns
import org.jetbrains.kotlin.backend.konan.RuntimeNames
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.ir.IrBuiltIns
import org.jetbrains.kotlin.ir.builders.IrBuilder
@@ -187,4 +188,6 @@ internal fun IrSymbol.findCStructDescriptor(interopBuiltIns: InteropBuiltIns): C
descriptor.findCStructDescriptor(interopBuiltIns)
internal fun DeclarationDescriptor.findCStructDescriptor(interopBuiltIns: InteropBuiltIns): ClassDescriptor? =
parentsWithSelf.filterIsInstance<ClassDescriptor>().firstOrNull { it.inheritsFromCStructVar(interopBuiltIns) }
parentsWithSelf.filterIsInstance<ClassDescriptor>().firstOrNull {
it.inheritsFromCStructVar(interopBuiltIns) || it.annotations.hasAnnotation(RuntimeNames.managedType)
}
@@ -5,19 +5,26 @@
package org.jetbrains.kotlin.backend.konan.ir.interop.cstruct
import org.jetbrains.kotlin.backend.konan.InteropBuiltIns
import org.jetbrains.kotlin.backend.konan.RuntimeNames
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.*
import org.jetbrains.kotlin.ir.IrBuiltIns
import org.jetbrains.kotlin.ir.builders.irBlockBody
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.*
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
import org.jetbrains.kotlin.ir.builders.declarations.buildValueParameter
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
import org.jetbrains.kotlin.ir.expressions.impl.IrDelegatingConstructorCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionExpressionImpl
import org.jetbrains.kotlin.ir.interpreter.toIrConst
import org.jetbrains.kotlin.ir.symbols.impl.IrFieldSymbolImpl
import org.jetbrains.kotlin.ir.types.classOrNull
import org.jetbrains.kotlin.ir.types.makeNullable
import org.jetbrains.kotlin.ir.types.typeWith
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi2ir.generators.GeneratorContext
internal class CStructVarClassGenerator(
@@ -30,6 +37,7 @@ internal class CStructVarClassGenerator(
override val irBuiltIns: IrBuiltIns = context.irBuiltIns
override val symbolTable: SymbolTable = context.symbolTable
override val typeTranslator: TypeTranslator = context.typeTranslator
val irFactory: IrFactory = context.irFactory
override val postLinkageSteps: MutableList<() -> Unit> = mutableListOf()
fun findOrGenerateCStruct(classDescriptor: ClassDescriptor, parent: IrDeclarationContainer): IrClass {
@@ -49,40 +57,247 @@ internal class CStructVarClassGenerator(
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<CallableMemberDescriptor>()
.filterNot { it.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE }
.map {
when (it) {
is PropertyDescriptor -> createProperty(it)
is SimpleFunctionDescriptor -> createFunction(it)
else -> null
.filterNot { it.isPrimary }
.map {
val constructor = createSecondaryConstructor(it)
irClass.addMember(constructor)
}
descriptor.unsubstitutedMemberScope
.getContributedDescriptors()
.filterIsInstance<CallableMemberDescriptor>()
.filterNot { it.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE }
.map {
when (it) {
is PropertyDescriptor -> createProperty(it)
is SimpleFunctionDescriptor -> createFunction(it)
else -> null
}
}
.filterNotNull()
.forEach(irClass::addMember)
}.also { irClass ->
if (irClass.descriptor.annotations.hasAnnotation(RuntimeNames.cppClass)) {
postLinkageSteps.add {
setupCppClass(irClass)
}
.filterNotNull()
.forEach(irClass::addMember)
}
if (irClass.descriptor.annotations.hasAnnotation(RuntimeNames.managedType)) {
postLinkageSteps.add {
setupManagedClass(irClass)
}
}
}
private fun createPrimaryConstructor(irClass: IrClass): IrConstructor {
val enumVarConstructorSymbol = symbolTable.referenceConstructor(
interopBuiltIns.cStructVar.unsubstitutedPrimaryConstructor!!
)
return createConstructor(irClass.descriptor.unsubstitutedPrimaryConstructor!!).also { irConstructor ->
postLinkageSteps.add {
irConstructor.body = irBuilder(irBuiltIns, irConstructor.symbol, SYNTHETIC_OFFSET, SYNTHETIC_OFFSET).irBlockBody {
+IrDelegatingConstructorCallImpl.fromSymbolOwner(
startOffset, endOffset,
context.irBuiltIns.unitType, enumVarConstructorSymbol
).also {
it.putValueArgument(0, irGet(irConstructor.valueParameters[0]))
private fun setupCppClass(irClass: IrClass) {
val companionDestroy = irClass.companionObject()!!.declarations
.filterIsInstance<IrSimpleFunction>()
.filter { it.name.toString() == "__destroy__" }
.singleOrNull() ?: return
val destroy = irClass.declarations
.filterIsInstance<IrSimpleFunction>()
.filter { it.name.toString() == "__destroy__" }
.single()
val getPtr = symbols.interopGetPtr
destroy.body = irBuilder(irBuiltIns, destroy.symbol, SYNTHETIC_OFFSET, SYNTHETIC_OFFSET)
.irBlockBody {
+irCall(companionDestroy).apply {
dispatchReceiver = irGetObject(irClass.companionObject()!!.symbol)
putValueArgument(0,
irCall(getPtr).apply {
extensionReceiver = irGet(destroy.dispatchReceiverParameter!!)
}
)
}
}
}
// TODO: move me to InteropLowering.kt?
private fun setupManagedClass(irClass: IrClass) {
// class Wrapper(cpp: CppClass, managed: Boolean) : ManagedType(cpp) {
// val managed = managed
// field cleaner = createCleaner(cpp) { it ->
// $Inner.Companion.__destroy__(it) // For general CPlusPlusClass
// or
// it.unref() // for SkiaRefCnt
// }
// }
val traceCleaners = false
val cppParam = irClass.primaryConstructor!!.valueParameters.first().also {
assert(it.name.toString() == "cpp")
}
val cppType = cppParam.type
val cppClass = cppType.classOrNull!!.owner
val superClassFqNames = cppClass.superTypes.map {
it.classOrNull?.owner?.fqNameWhenAvailable
}.filterNotNull()
val isSkiaRefCnt = superClassFqNames.contains(RuntimeNames.skiaRefCnt)
val managedVal = irClass.declarations
.filterIsInstance<IrProperty>()
.filter { it.name.toString() == "managed" }
.single()
val managedValType = managedVal.getter!!.returnType
managedVal.backingField = symbolTable.declareField(
SYNTHETIC_OFFSET,
SYNTHETIC_OFFSET,
IrDeclarationOrigin.PROPERTY_BACKING_FIELD,
managedVal.descriptor,
managedValType,
DescriptorVisibilities.PRIVATE
).also {
it.parent = irClass
it.initializer = irBuilder(irBuiltIns, it.symbol, SYNTHETIC_OFFSET, SYNTHETIC_OFFSET).run {
irExprBody(irGet(irClass.primaryConstructor!!.valueParameters[1]))
}
}
managedVal.getter!!.body = irBuilder(irBuiltIns, managedVal.getter!!.symbol, SYNTHETIC_OFFSET, SYNTHETIC_OFFSET)
.irBlockBody {
+irReturn(irGetField(irGet(managedVal.getter!!.dispatchReceiverParameter!!), managedVal.backingField!!))
}
val cleanerField = irFactory.createField(
SYNTHETIC_OFFSET,
SYNTHETIC_OFFSET,
IrDeclarationOrigin.DEFINED,
IrFieldSymbolImpl(),
Name.identifier("cleaner"),
symbols.createCleaner.owner.returnType,
DescriptorVisibilities.PRIVATE,
isFinal = true,
isExternal = false,
isStatic = false
).also { field ->
field.parent = irClass
field.initializer = irBuilder(irBuiltIns, field.symbol, SYNTHETIC_OFFSET, SYNTHETIC_OFFSET).run {
val lambda = context.irFactory.buildFun {
startOffset = SYNTHETIC_OFFSET
endOffset = SYNTHETIC_OFFSET
origin = IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA
name = Name.special("<anonymous>")
visibility = DescriptorVisibilities.LOCAL
returnType = irBuiltIns.unitType
}.apply {
parent = field
valueParameters = listOf (
buildValueParameter(this) {
origin = IrDeclarationOrigin.DEFINED
name = Name.identifier("field")
index = 0
type = cppType
}
)
body = irBlockBody {
val itCpp = irGet(valueParameters.single())
if (traceCleaners) {
+irCall(symbols.println).apply {
putValueArgument(0,
"Cleaning ${irClass.name} with ${if (isSkiaRefCnt) "unref" else "__destroy__"}"
.toIrConst(irBuiltIns.stringType)
)
}
}
if (isSkiaRefCnt) {
val unref = cppClass.declarations
.filterIsInstance<IrSimpleFunction>()
.single { it.name.toString() == "unref" }
+irCall(unref).apply {
dispatchReceiver = itCpp
}
} else {
val destroy = cppClass.declarations
.filterIsInstance<IrSimpleFunction>()
.singleOrNull() { it.name.toString() == "__destroy__" }
if (destroy!= null) {
+irCall(destroy).apply {
dispatchReceiver = itCpp
}
}
val nativeHeap = symbols.nativeHeap
val free = nativeHeap.owner.declarations
.filterIsInstance<IrSimpleFunction>()
.single { it.name.toString() == "free" }
+irCall(free).apply {
dispatchReceiver = irGetObject(nativeHeap)
putValueArgument(0,
irCall(symbols.interopNativePointedGetRawPointer).apply {
extensionReceiver = itCpp
}
)
}
// TODO: need to nativePlacement.free(cpp.rawPtr)
// TODO: } // managed
}
}
}
val callCreateCleaner = irCall(symbols.createCleaner).apply {
dispatchReceiver = null
putTypeArgument(0, cppType)
putValueArgument(0,
irGet(irClass.primaryConstructor!!.valueParameters[0])
)
putValueArgument(1,
IrFunctionExpressionImpl(
startOffset = SYNTHETIC_OFFSET,
endOffset = SYNTHETIC_OFFSET,
type = irBuiltIns.functionN(1).typeWith(cppType, irBuiltIns.unitType),
origin = IrStatementOrigin.LAMBDA,
function = lambda
)
)
}
irExprBody(irIfThenElse(callCreateCleaner.type.makeNullable(), irGet(irClass.primaryConstructor!!.valueParameters[1]), callCreateCleaner, irNull()))
}
}
irClass.declarations += cleanerField
}
private fun createPrimaryConstructor(irClass: IrClass): IrConstructor {
if (!irClass.descriptor.annotations.hasAnnotation(RuntimeNames.managedType)) {
val cStructVarConstructorSymbol = symbolTable.referenceConstructor(
interopBuiltIns.cStructVar.unsubstitutedPrimaryConstructor!!
)
return createConstructor(irClass.descriptor.unsubstitutedPrimaryConstructor!!).also { irConstructor ->
postLinkageSteps.add {
irConstructor.body = irBuilder(irBuiltIns, irConstructor.symbol, SYNTHETIC_OFFSET, SYNTHETIC_OFFSET).irBlockBody {
+IrDelegatingConstructorCallImpl.fromSymbolOwner(
startOffset, endOffset,
context.irBuiltIns.unitType, cStructVarConstructorSymbol
).also {
it.putValueArgument(0, irGet(irConstructor.valueParameters[0]))
}
+irInstanceInitializer(symbolTable.referenceClass(irClass.descriptor))
}
}
}
} else {
return createConstructor(irClass.descriptor.unsubstitutedPrimaryConstructor!!).also { irConstructor ->
val managedTypeConstructor = symbolTable.referenceConstructor(
interopBuiltIns.managedType.unsubstitutedPrimaryConstructor!!
)
postLinkageSteps.add {
irConstructor.body = irBuilder(irBuiltIns, irConstructor.symbol, SYNTHETIC_OFFSET, SYNTHETIC_OFFSET).irBlockBody {
+IrDelegatingConstructorCallImpl.fromSymbolOwner(
startOffset, endOffset,
context.irBuiltIns.unitType, managedTypeConstructor
).also {
it.putTypeArgument(0, irConstructor.valueParameters[0].type)
it.putValueArgument(0, irGet(irConstructor.valueParameters[0]))
}
+irInstanceInitializer(symbolTable.referenceClass(irClass.descriptor))
}
+irInstanceInitializer(symbolTable.referenceClass(irClass.descriptor))
}
}
}
@@ -97,4 +312,4 @@ internal class CStructVarClassGenerator(
}
}
}
}
}
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.backend.konan.ir.interop.cstruct
import org.jetbrains.kotlin.backend.konan.InteropBuiltIns
import org.jetbrains.kotlin.backend.konan.RuntimeNames
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
@@ -20,10 +21,7 @@ import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrConstructor
import org.jetbrains.kotlin.ir.declarations.addMember
import org.jetbrains.kotlin.ir.expressions.impl.IrDelegatingConstructorCallImpl
import org.jetbrains.kotlin.ir.util.SYNTHETIC_OFFSET
import org.jetbrains.kotlin.ir.util.SymbolTable
import org.jetbrains.kotlin.ir.util.TypeTranslator
import org.jetbrains.kotlin.ir.util.irBuilder
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi2ir.generators.GeneratorContext
@@ -41,39 +39,56 @@ internal class CStructVarCompanionGenerator(
fun generate(structDescriptor: ClassDescriptor): IrClass =
createClass(structDescriptor.companionObjectDescriptor!!) { companionIrClass ->
val annotation = companionIrClass.descriptor.annotations
.findAnnotation(varTypeAnnotationFqName)!!
val size = annotation.getArgumentValueOrNull<Long>("size")!!
val align = annotation.getArgumentValueOrNull<Int>("align")!!
companionIrClass.addMember(createCompanionConstructor(companionIrClass.descriptor, size, align))
if (structDescriptor.annotations.hasAnnotation(RuntimeNames.managedType)) {
companionIrClass.addMember(createCompanionConstructor(companionIrClass.descriptor, 0L, 0))
} else {
val annotation = companionIrClass.descriptor.annotations
.findAnnotation(varTypeAnnotationFqName)!!
val size = annotation.getArgumentValueOrNull<Long>("size")!!
val align = annotation.getArgumentValueOrNull<Int>("align")!!
companionIrClass.addMember(createCompanionConstructor(companionIrClass.descriptor, size, align))
}
companionIrClass.descriptor.unsubstitutedMemberScope
.getContributedDescriptors()
.filterIsInstance<CallableMemberDescriptor>()
.filterNot { it.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE }
.mapNotNull {
when (it) {
is PropertyDescriptor -> createProperty(it)
is SimpleFunctionDescriptor -> createFunction(it)
else -> null
.getContributedDescriptors()
.filterIsInstance<CallableMemberDescriptor>()
.filterNot { it.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE }
.mapNotNull {
when (it) {
is PropertyDescriptor -> createProperty(it)
is SimpleFunctionDescriptor -> createFunction(it)
else -> null
}
}
}
.forEach(companionIrClass::addMember)
.forEach(companionIrClass::addMember)
}
private fun createCompanionConstructor(companionObjectDescriptor: ClassDescriptor, size: Long, align: Int): IrConstructor {
val superConstructorSymbol = symbolTable.referenceConstructor(interopBuiltIns.cStructVarType.unsubstitutedPrimaryConstructor!!)
return createConstructor(companionObjectDescriptor.unsubstitutedPrimaryConstructor!!).also { irConstructor ->
postLinkageSteps.add {
irConstructor.body = irBuilder(irBuiltIns, irConstructor.symbol, SYNTHETIC_OFFSET, SYNTHETIC_OFFSET).irBlockBody {
+IrDelegatingConstructorCallImpl.fromSymbolOwner(
startOffset, endOffset, context.irBuiltIns.unitType,
superConstructorSymbol
).also {
it.putValueArgument(0, irLong(size))
it.putValueArgument(1, irInt(align))
if (companionObjectDescriptor.containingDeclaration.annotations.hasAnnotation(RuntimeNames.managedType)) {
return createConstructor(companionObjectDescriptor.unsubstitutedPrimaryConstructor!!).also { irConstructor ->
postLinkageSteps.add {
irConstructor.body = irBuilder(irBuiltIns, irConstructor.symbol, SYNTHETIC_OFFSET, SYNTHETIC_OFFSET).irBlockBody {
+IrDelegatingConstructorCallImpl.fromSymbolOwner(
startOffset, endOffset, context.irBuiltIns.unitType,
irBuiltIns.anyClass.owner.primaryConstructor!!.symbol
)
+irInstanceInitializer(symbolTable.referenceClass(companionObjectDescriptor))
}
}
}
} else {
val superConstructorSymbol = symbolTable.referenceConstructor(interopBuiltIns.cStructVarType.unsubstitutedPrimaryConstructor!!)
return createConstructor(companionObjectDescriptor.unsubstitutedPrimaryConstructor!!).also { irConstructor ->
postLinkageSteps.add {
irConstructor.body = irBuilder(irBuiltIns, irConstructor.symbol, SYNTHETIC_OFFSET, SYNTHETIC_OFFSET).irBlockBody {
+IrDelegatingConstructorCallImpl.fromSymbolOwner(
startOffset, endOffset, context.irBuiltIns.unitType,
superConstructorSymbol
).also {
it.putValueArgument(0, irLong(size))
it.putValueArgument(1, irInt(align))
}
+irInstanceInitializer(symbolTable.referenceClass(companionObjectDescriptor))
}
+irInstanceInitializer(symbolTable.referenceClass(companionObjectDescriptor))
}
}
}
@@ -7,9 +7,9 @@ package org.jetbrains.kotlin.backend.konan.llvm
import kotlinx.cinterop.*
import llvm.*
import org.jetbrains.kotlin.backend.common.ir.ir2string
import org.jetbrains.kotlin.backend.common.ir.allParameters
import org.jetbrains.kotlin.backend.common.ir.allParametersCount
import org.jetbrains.kotlin.backend.common.ir.ir2string
import org.jetbrains.kotlin.backend.common.lower.inline.InlinerExpressionLocationHint
import org.jetbrains.kotlin.backend.konan.*
import org.jetbrains.kotlin.backend.konan.cgen.CBridgeOrigin
@@ -228,7 +228,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
* During function code generation [FunctionScope] should be set up.
*/
private object TopLevelCodeContext : CodeContext {
private fun unsupported(any: Any? = null): Nothing = throw UnsupportedOperationException(any?.toString() ?: "")
private fun unsupported(any: Any? = null): Nothing = throw UnsupportedOperationException(if (any is IrElement) any.render() else any?.toString() ?: "")
override fun genReturn(target: IrSymbolOwner, value: LLVMValueRef?) = unsupported(target)
@@ -910,12 +910,12 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
//-------------------------------------------------------------------------//
private fun evaluateGetObjectValue(value: IrGetObjectValue): LLVMValueRef =
functionGenerationContext.getObjectValue(
value.symbol.owner,
currentCodeContext.exceptionHandler,
value.startLocation,
value.endLocation
)
functionGenerationContext.getObjectValue(
value.symbol.owner,
currentCodeContext.exceptionHandler,
value.startLocation,
value.endLocation
)
//-------------------------------------------------------------------------//
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.IrValueDeclaration
import org.jetbrains.kotlin.ir.declarations.IrVariable
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.name.Name
internal fun IrElement.needDebugInfo(context: Context) = context.shouldContainDebugInfo() || (this is IrVariable && this.isVar)
@@ -70,7 +71,9 @@ internal class VariableManager(val functionGenerationContext: FunctionGeneration
internal fun createMutable(valueDeclaration: IrValueDeclaration,
isVar: Boolean, value: LLVMValueRef? = null, variableLocation: VariableDebugLocation?) : Int {
assert(!contextVariablesToIndex.contains(valueDeclaration))
assert(!contextVariablesToIndex.contains(valueDeclaration)) {
"Could not find ${valueDeclaration.render()} in contextVariablesToIndex"
}
val index = variables.size
val type = functionGenerationContext.getLLVMType(valueDeclaration.type)
val slot = functionGenerationContext.alloca(type, valueDeclaration.name.asString(), variableLocation)
@@ -29,6 +29,7 @@ import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrDelegatingConstructorCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionReferenceImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrReturnImpl
import org.jetbrains.kotlin.ir.interpreter.toIrConst
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
@@ -766,11 +767,31 @@ private class InteropTransformer(val context: Context, override val irFile: IrFi
private fun generateCFunctionPointer(function: IrSimpleFunction, expression: IrExpression): IrExpression =
generateWithStubs { generateCFunctionPointer(function, function, expression) }
// ?.foo() part
fun IrBuilderWithScope.irSafeCall(extensionReceiverExpression: IrExpression, typeArguments: List<IrTypeArgument>, callee: IrSimpleFunctionSymbol): IrExpression =
irBlock {
val tmp = irTemporary(extensionReceiverExpression)
+irIfThenElse(callee.owner.returnType.makeNullable(),
irEqeqeq(irGet(tmp), irNull()),
irNull(),
irCall(callee).apply {
extensionReceiver = irGet(tmp)
typeArguments.forEachIndexed { index, arg ->
putTypeArgument(index, arg.typeOrNull!!)
}
}
)
}
override fun visitConstructorCall(expression: IrConstructorCall): IrExpression {
expression.transformChildrenVoid(this)
if (expression.symbol.owner.hasCCallAnnotation("CppClassConstructor")) {
return transformSecondaryCppConstructorCall(expression)
return transformCppConstructorCall(expression)
}
if (expression.symbol.owner.constructedClass.hasAnnotation(RuntimeNames.managedType)) {
return transformManagedCppConstructorCall(expression)
}
val callee = expression.symbol.owner
@@ -806,8 +827,10 @@ private class InteropTransformer(val context: Context, override val irFile: IrFi
}
}
private fun transformSecondaryCppConstructorCall(expression: IrConstructorCall): IrExpression {
private fun transformCppConstructorCall(expression: IrConstructorCall): IrExpression {
val irConstructor = expression.symbol.owner
if (irConstructor.isPrimary) return expression
val irClass = irConstructor.constructedClass
val primaryConstructor = irClass.primaryConstructor!!.symbol
@@ -844,6 +867,9 @@ private class InteropTransformer(val context: Context, override val irFile: IrFi
putValueArgument(0,
irCall(interopGetPtr).apply {
extensionReceiver = irGet(tmp)
putTypeArgument(0,
(correspondingInit.valueParameters.first().type as IrSimpleType).arguments.single().typeOrNull!!
)
}
)
for (index in 0 until expression.valueArgumentsCount) {
@@ -858,6 +884,58 @@ private class InteropTransformer(val context: Context, override val irFile: IrFi
return irBlock
}
private fun IrBuilderWithScope.transformManagedArguments(oldCall: IrFunctionAccessExpression, oldFunction: IrFunction, newCall: IrFunctionAccessExpression, newFunction: IrFunction) {
for (index in 0 until oldCall.valueArgumentsCount) {
val newArgument = irBlock {
val oldArgument = irTemporary(oldCall.getValueArgument(index)!!)
if (oldFunction.valueParameters[index].type.isManagedType()) {
+irSafeCall(
irGet(oldArgument),
listOf((newFunction.valueParameters[index].type as IrSimpleType).arguments.single()),
symbols.interopManagedGetPtr
// symbols.interopGetPtr
)
} else {
+irGet(oldArgument)
}
}
newCall.putValueArgument(index, newArgument)
}
}
private fun transformManagedCppConstructorCall(expression: IrConstructorCall): IrExpression {
val irConstructor = expression.symbol.owner
if (irConstructor.isPrimary) return expression
val irClass = irConstructor.constructedClass
val primaryConstructor = irClass.primaryConstructor!!.symbol
val correspondingCppClass = primaryConstructor.owner.valueParameters.first().type.classOrNull?.owner!!
val correspondingCppConstructor = correspondingCppClass
.declarations
.filterIsInstance<IrConstructor>()
.filter { it.valueParameters.size == irConstructor.valueParameters.size}
.singleOrNull {
it.valueParameters.mapIndexed() { index, initParameter ->
managedTypeMatch(irConstructor.valueParameters[index].type, initParameter.type)
}.all{ it }
} ?: error("Could not find a match for ${irConstructor.render()}")
val irBlock = builder.at(expression)
.irBlock {
val cppConstructorCall = irCall(correspondingCppConstructor.symbol).apply {
transformManagedArguments(expression, irConstructor, this, correspondingCppConstructor)
}
val call = irCall(primaryConstructor).also {
it.putValueArgument(0, transformCppConstructorCall(cppConstructorCall))
it.putValueArgument(1, true.toIrConst(context.irBuiltIns.booleanType))
}
+call
}
return irBlock
}
/**
* Handle `const val`s that come from interop libraries.
*
@@ -936,6 +1014,15 @@ private class InteropTransformer(val context: Context, override val irFile: IrFi
return generateCCall(expression)
}
// TODO: what's the proper condition?
val funcClass = function.dispatchReceiverParameter?.type?.classOrNull?.owner
if (funcClass?.hasAnnotation(RuntimeNames.managedType) ?: false) {
return transformManagedCall(expression)
}
if ((funcClass?.isCompanion == true) && ((funcClass.parent as? IrClass)?.hasAnnotation(RuntimeNames.managedType) ?: false)) {
return transformManagedCompanionCall(expression)
}
val failCompilation = { msg: String -> error(irFile, expression, msg) }
tryGenerateInteropMemberAccess(expression, symbols, builder, failCompilation)?.let { return it }
@@ -1080,6 +1167,156 @@ private class InteropTransformer(val context: Context, override val irFile: IrFi
}
}
private fun IrType.isManagedType() = this.isSubtypeOfClass(symbols.interopManagedType)
private fun IrType.isCPlusPlusClass() = this.isSubtypeOfClass(symbols.interopCPlusPlusClass)
private fun IrType.isSkiaRefCnt() = this.isSubtypeOfClass(symbols.interopSkiaRefCnt)
private fun transformManagedCall(expression: IrCall): IrExpression {
val function = expression.symbol.owner
val irClass = function.dispatchReceiverParameter!!.type.classOrNull!!.owner
val cppProperty = irClass.declarations
.filterIsInstance<IrProperty>()
.filter { it.name.toString() == "cpp" }
.single()
val managedProperty = irClass.declarations
.filterIsInstance<IrProperty>()
.filter { it.name.toString() == "managed" }
.single()
if (function == cppProperty.getter || function == managedProperty.getter) return expression
val cppParam = irClass.primaryConstructor!!.valueParameters.first().also {
assert(it.name.toString() == "cpp")
}
val cppType = cppParam.type
val cppClass = cppType.classOrNull!!.owner
val newFunction = cppClass.declarations
.filterIsInstance<IrSimpleFunction>()
.filter { it.name == function.name }
.filter { it.valueParameters.size == function.valueParameters.size }
.filter {
it.valueParameters.mapIndexed() { index, parameter ->
managedTypeMatch(function.valueParameters[index].type, parameter.type)
}.all { it }
}.singleOrNull() ?: error("Could not find ${function.name} in ${cppClass}")
val newFunctionType = newFunction.returnType
val newCall = with (builder.at(expression)) {
irCall(newFunction).apply {
dispatchReceiver = irCall(cppProperty.getter!!).apply {
dispatchReceiver = expression.dispatchReceiver
}
transformManagedArguments(expression, function, this, newFunction)
}
}
val ccall = generateCCall(newCall as IrCall)
return if (function.returnType.isManagedType()) {
assert(newFunctionType.isCPointer(symbols))
val pointed = (newFunctionType as IrSimpleType).arguments.single().typeOrNull!!
with (builder.at(ccall)) {
irCall(function.returnType.classOrNull!!.owner.primaryConstructor!!.symbol).apply {
val managed = when {
pointed.isSkiaRefCnt() -> true
pointed.isCPlusPlusClass() -> false
else -> error("Unexpected pointer argument for ManagedType")
}.toIrConst(context.irBuiltIns.booleanType)
putValueArgument(0,
irCall(symbols.interopInterpretNullablePointed).apply {
putValueArgument(0,
irCall(symbols.interopCPointerGetRawValue).apply {
extensionReceiver = ccall
}
)
putTypeArgument(0, pointed)
}
)
putValueArgument(1, managed)
}
}
} else {
ccall
}
}
private fun managedTypeMatch(one: IrType, another: IrType): Boolean {
if (one == another) return true
if (one.classOrNull?.owner?.hasAnnotation(RuntimeNames.managedType) != true) return false
if (!another.isCPointer(symbols) && !another.isCValuesRef(symbols)) return false
val cppType = one.classOrNull!!.owner.primaryConstructor?.valueParameters?.first()?.type ?: return false
val pointedType = (another as? IrSimpleType)?.arguments?.single() as? IrSimpleType ?: return false
return cppType == pointedType
}
private fun transformManagedCompanionCall(expression: IrCall): IrExpression {
val function = expression.symbol.owner
val companion = function.parent as IrClass
assert(companion.isCompanion)
val cppInClass = (companion.parent as IrClass).declarations
.filterIsInstance<IrProperty>()
.filter { it.name.toString() == "cpp" }
.single()
val cppCompanion = cppInClass.getter!!.returnType.classOrNull!!.owner
.declarations
.filterIsInstance<IrClass>()
.single{ it.isCompanion }
val newFunction = cppCompanion.declarations
.filterIsInstance<IrSimpleFunction>()
.filter { it.name == function.name }
.filter { it.valueParameters.size == function.valueParameters.size }
.filter {
it.valueParameters.mapIndexed() { index, parameter ->
managedTypeMatch(function.valueParameters[index].type, parameter.type)
}.all { it }
}.single()
val newFunctionType = newFunction.returnType
val newCall = with (builder.at(expression)) {
irCall(newFunction).apply {
dispatchReceiver = irGetObject(cppCompanion.symbol)
transformManagedArguments(expression, function, this, newFunction)
}
}
// TODO: this is exactly the same code as in transformManagedCall
val ccall = generateCCall(newCall as IrCall)
return if (function.returnType.isManagedType()) {
assert(newFunctionType.isCPointer(symbols))
val pointed = (newFunctionType as IrSimpleType).arguments.single().typeOrNull!!
with (builder.at(ccall)) {
irCall(function.returnType.classOrNull!!.constructors.single { it.owner.isPrimary }).apply {
val managed = when {
pointed.isCPlusPlusClass() -> false
pointed.isSkiaRefCnt() -> true
else -> error("Unexpected pointer argument for ManagedType")
}.toIrConst(context.irBuiltIns.booleanType)
putValueArgument(0,
irCall(symbols.interopInterpretNullablePointed).apply {
putValueArgument(0,
irCall(symbols.interopCPointerGetRawValue).apply {
extensionReceiver = ccall
}
)
putTypeArgument(0, pointed)
}
)
putValueArgument(1, managed)
}
}
} else {
ccall
}
}
private fun IrBuilderWithScope.irConvertInteger(
source: IrClassSymbol,
target: IrClassSymbol,
@@ -3906,6 +3906,10 @@ createInterop("cppSkia") {
it.defFile 'interop/cpp/skia.def'
}
createInterop("cppSkiaSignature") {
it.defFile 'interop/cpp/skiaSignature.def'
}
createInterop("threadStates") {
it.defFile "interop/threadStates/threadStates.def"
it.extraOpts "-Xcompile-source", "$projectDir/interop/threadStates/threadStates.cpp"
@@ -4320,10 +4324,26 @@ interopTest("interop_cppTypes") {
}
interopTest("interop_cppSkia") {
disabled = (project.testTarget == 'wasm32') // No interop for wasm yet.
disabled = (project.testTarget == 'wasm32' || // No interop for wasm yet.
isExperimentalMM)
source = "interop/cpp/skia.kt"
interop = 'cppSkia'
goldValue = "17 17\n"
goldValue =
"SkFoo()\nSkData()\nSkData()\nSkValue()\nSkData()\n" +
"MANAGED: f: true, a: true, b: false, v: true, c: false\n" +
"DATA: 17 19 17 17\n" +
"unref\n~SkData()\n~SkFoo()\n"
UtilsKt.dependsOnPlatformLibs(it)
}
interopTest("interop_cppSkiaSignature") {
disabled = (project.testTarget == 'wasm32') // No interop for wasm yet.
source = "interop/cpp/skiaSignature.kt"
interop = "cppSkiaSignature"
goldValue =
"SkData()\nSkData(19)\nSkData(SkData*)\nSkData(SkData*, SkData*)\nSkData(200)\nSkData(436)\n"+
"true true true true true\n"
UtilsKt.dependsOnPlatformLibs(it)
}
/**
@@ -1,6 +1,6 @@
language = C++
compilerOpts = -std=c++14
plugin = org.jetbrains.kotlin.native.interop.skia
---
class CppTest {
@@ -1,6 +1,8 @@
language = C++
compilerOpts = -std=c++17
plugin = org.jetbrains.kotlin.native.interop.skia
package = org.jetbrains.skiko.skia.native
headers = stdio.h
---
// TODO: this one checks the syntactic aspect for now.
@@ -22,20 +24,64 @@ template <typename T> sk_sp<T> sk_ref_sp(T* obj) {
return sk_sp<T>(obj);
}
class Value {
public:
int data;
class SkValue {
public:
SkValue() {
printf("SkValue()\n");
};
void unref() {
printf("unref\n");
};
int data;
void setData(int val) {
data = val;
};
};
class Foo {
public:
Foo() { }
class SkData {
public:
SkData() {
printf("SkData()\n");
};
virtual sk_sp<Value> foo(Value *obj) {
return sk_sp<Value>(obj);
}
~SkData() {
printf("~SkData()\n");
};
virtual Value* bar(sk_sp<Value> obj) {
return obj.release();
}
int data;
void setData(int val) {
data = val;
};
};
class SkFoo {
public:
SkFoo() {
printf("SkFoo()\n");
};
~SkFoo() {
printf("~SkFoo()\n");
};
virtual sk_sp<SkValue> foo(SkData *obj) {
SkValue* value = new SkValue();
value->setData(obj->data);
return sk_sp<SkValue>(value);
};
virtual SkData* bar(sk_sp<SkValue> obj) {
SkValue* value = obj.release();
SkData* data = new SkData();
data->setData(value->data);
return data;
};
virtual SkData* qux() {
return new SkData();
};
};
@@ -1,13 +1,32 @@
@file:Suppress("EXPERIMENTAL_API_USAGE_ERROR")
import kotlinx.cinterop.*
import kotlin.test.*
import kotlin.native.internal.*
import skia.*
import org.jetbrains.skiko.skia.native.*
import platform.posix.printf
fun main() {
// TODO: the test used to work with forceCheckedShutdown,
// but it is broken now. Revisit after it is fixed.
// kotlin.native.internal.Debugging.forceCheckedShutdown = true
Platform.isCleanersLeakCheckerActive = true
val f = Foo()
val a = nativeHeap.alloc<Value>()
a.data = 17
val x = f.foo(a.ptr)
val z = f.bar(x)
println("${a?.data} ${z?.pointed?.data}")
val a = Data()
a.setData(17)
val b = f.qux()!!
b.setData(19)
val v = f.foo(a)
val c = f.bar(v)!!
// Use printf instead of println to avoid messages
// appearing out of order with the native code.
// The native code uses printf.
printf("MANAGED: f: ${f.managed}, a: ${a.managed}, b: ${b.managed}, v: ${v.managed}, c: ${c.managed}\n")
printf("DATA: ${a.cpp.data} ${b.cpp.data} ${v.cpp.data} ${c.cpp.data}\n")
}
@@ -0,0 +1,61 @@
language = C++
compilerOpts = -std=c++17
plugin = org.jetbrains.kotlin.native.interop.skia
package = org.jetbrains.skiko.skia.native
headers = stdio.h
---
// TODO: this one checks the syntactic aspect for now.
// To be updated for proper c++ destructor and
// kotlin garbage collection interaction.
template <typename T> class sk_sp {
public:
sk_sp(T* obj) : data(obj) {}
T* release() {
return data;
}
private:
T* data;
};
template <typename T> sk_sp<T> sk_ref_sp(T* obj) {
return sk_sp<T>(obj);
}
class SkData {
public:
SkData() {
printf("SkData()\n");
};
SkData(int val) {
printf("SkData(%d)\n", val);
data = val + 100;
};
SkData(SkData* obj) {
printf("SkData(SkData*)\n");
data = obj->data + 200;
};
SkData(SkData* obj, SkData* obj2) {
printf("SkData(SkData*, SkData*)\n");
data = obj->data + obj2->data + 300;
};
SkData* foo(SkData* obj, SkData* obj2) {
return new SkData(obj->data + obj2->data + data);
};
int data;
void setData(int val) {
data = val;
};
int checkData(int val) {
return val == data;
};
};
@@ -0,0 +1,28 @@
@file:Suppress("EXPERIMENTAL_API_USAGE_ERROR")
import kotlinx.cinterop.*
import kotlin.test.*
import kotlin.native.internal.*
import org.jetbrains.skiko.skia.native.*
import platform.posix.printf
fun main() {
val a = Data()
a.setData(17)
val b = Data(19)
val c = Data(a)
val d = Data(a, b)
val e = Data(200).foo(a, b)!!
val a1 = a.checkData(17) != 0
val b1 = b.checkData(119) != 0
val c1 = c.checkData(217) != 0
val d1 = d.checkData(436) != 0
val e1 = e.checkData(536) != 0
// Use printf instead of println to avoid messages
// appearing out of order with the native code.
// The native code uses printf.
printf("$a1 $b1 $c1 $d1 $e1\n")
}
@@ -1,5 +1,6 @@
language = C++
compilerOpts = -std=c++14
plugin = org.jetbrains.kotlin.native.interop.skia
package = cpptypes
---