diff --git a/kotlin-native/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ManagedType.kt b/kotlin-native/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ManagedType.kt new file mode 100644 index 00000000000..1d9630d0d2a --- /dev/null +++ b/kotlin-native/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ManagedType.kt @@ -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(val cpp: T) + +val ManagedType.ptr: CPointer get() = this.cpp.ptr 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 ed8e5697b17..b81351e2099 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 @@ -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 diff --git a/kotlin-native/Interop/Skia/SKIA_PLUGIN.md b/kotlin-native/Interop/Skia/SKIA_PLUGIN.md index 512233675d3..66011d62751 100644 --- a/kotlin-native/Interop/Skia/SKIA_PLUGIN.md +++ b/kotlin-native/Interop/Skia/SKIA_PLUGIN.md @@ -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 ====================== 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 index b7fb4a4d1ed..4cedc054c58 100644 --- 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 @@ -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)) + } } \ No newline at end of file diff --git a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/DeepCopyForManagedWrapper.kt b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/DeepCopyForManagedWrapper.kt new file mode 100644 index 00000000000..a7d682a5828 --- /dev/null +++ b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/DeepCopyForManagedWrapper.kt @@ -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 +} 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 6d6f8e8c148..f4f406d7baf 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 @@ -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) } } 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 a871c7c5e45..0ad20d69bd0 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 @@ -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) : @@ -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 = emptyList(), - methods: List = emptyList(), + val constructors: List = emptyList(), + val methods: List = emptyList(), override val superClassInit: SuperClassInit? = null, override val interfaces: List = emptyList(), override val properties: List = emptyList(), @@ -343,7 +350,7 @@ sealed class ClassStub : StubContainer(), StubElementWithOrigin, AnnotationHolde class Companion( override val classifier: Classifier, - methods: List = emptyList(), + val methods: List = emptyList(), override val superClassInit: SuperClassInit? = null, override val interfaces: List = emptyList(), override val properties: List = emptyList(), 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 23dc2840522..c250d74e92c 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 @@ -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( 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 f6c3cbb8ca8..1f9d5a5db0b 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,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 = def.methods .filter { it.isCxxInstanceMethod } @@ -100,7 +114,7 @@ internal class StructStubBuilder( } catch (e: Throwable) { null } - }.filterNotNull() + }.filterNotNull() + destructor val fields: List = 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 { 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 daa1f6930c3..a710d02c183 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 @@ -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) 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 62a37c5b747..cf669376c96 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 @@ -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 -> diff --git a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrVisitor.kt b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrVisitor.kt index 12c33e7d811..aa9f96fd7c8 100644 --- a/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrVisitor.kt +++ b/kotlin-native/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/StubIrVisitor.kt @@ -19,4 +19,28 @@ interface StubIrVisitor { fun visitPropertyAccessor(propertyAccessor: PropertyAccessor, data: T): R fun visitSimpleStubContainer(simpleStubContainer: SimpleStubContainer, data: T): R -} \ No newline at end of file +} + + +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 + +} 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 3a060402d65..a759f968f86 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 @@ -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) = 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 ddc05980381..3389f8ceb42 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 @@ -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") } 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 ca30c7ff676..3a2fb3c0bd3 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 @@ -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, 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. 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 9bd24c8d679..9c12c3a4544 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 @@ -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) 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 fade3af6f40..896459f0a3e 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 @@ -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") 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 d9b04afcbb4..bd9ddd65c6b 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 @@ -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().firstOrNull { it.inheritsFromCStructVar(interopBuiltIns) } \ No newline at end of file + parentsWithSelf.filterIsInstance().firstOrNull { + it.inheritsFromCStructVar(interopBuiltIns) || it.annotations.hasAnnotation(RuntimeNames.managedType) + } \ 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/CStructVarClassGenerator.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/interop/cstruct/CStructVarClassGenerator.kt index 4a4276a1a5f..42042693c85 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,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() - .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() + .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() + .filter { it.name.toString() == "__destroy__" } + .singleOrNull() ?: return + + val destroy = irClass.declarations + .filterIsInstance() + .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() + .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("") + 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() + .single { it.name.toString() == "unref" } + +irCall(unref).apply { + dispatchReceiver = itCpp + } + } else { + val destroy = cppClass.declarations + .filterIsInstance() + .singleOrNull() { it.name.toString() == "__destroy__" } + if (destroy!= null) { + +irCall(destroy).apply { + dispatchReceiver = itCpp + } + } + val nativeHeap = symbols.nativeHeap + val free = nativeHeap.owner.declarations + .filterIsInstance() + .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( } } } -} \ 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 224317b9dfd..3ed9589f6fa 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 @@ -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("size")!! - val align = annotation.getArgumentValueOrNull("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("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 + .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) + .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)) } } } diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt index 2c242f5da4c..a19e52bae3d 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt @@ -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, 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() + .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() + .filter { it.name.toString() == "cpp" } + .single() + + val managedProperty = irClass.declarations + .filterIsInstance() + .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() + .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() + .filter { it.name.toString() == "cpp" } + .single() + + val cppCompanion = cppInClass.getter!!.returnType.classOrNull!!.owner + .declarations + .filterIsInstance() + .single{ it.isCompanion } + + val newFunction = cppCompanion.declarations + .filterIsInstance() + .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, diff --git a/kotlin-native/backend.native/tests/build.gradle b/kotlin-native/backend.native/tests/build.gradle index 86ff49ebd1c..cf0addcf78e 100644 --- a/kotlin-native/backend.native/tests/build.gradle +++ b/kotlin-native/backend.native/tests/build.gradle @@ -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) } /** diff --git a/kotlin-native/backend.native/tests/interop/cpp/cppClass.def b/kotlin-native/backend.native/tests/interop/cpp/cppClass.def index d3698dda5a6..5238de60c35 100644 --- a/kotlin-native/backend.native/tests/interop/cpp/cppClass.def +++ b/kotlin-native/backend.native/tests/interop/cpp/cppClass.def @@ -1,6 +1,6 @@ language = C++ compilerOpts = -std=c++14 - +plugin = org.jetbrains.kotlin.native.interop.skia --- class CppTest { diff --git a/kotlin-native/backend.native/tests/interop/cpp/skia.def b/kotlin-native/backend.native/tests/interop/cpp/skia.def index a1d02dc8c01..9fb5e5f77fd 100644 --- a/kotlin-native/backend.native/tests/interop/cpp/skia.def +++ b/kotlin-native/backend.native/tests/interop/cpp/skia.def @@ -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 sk_sp sk_ref_sp(T* obj) { return sk_sp(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 foo(Value *obj) { - return sk_sp(obj); - } + ~SkData() { + printf("~SkData()\n"); + }; - virtual Value* bar(sk_sp 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 foo(SkData *obj) { + SkValue* value = new SkValue(); + value->setData(obj->data); + return sk_sp(value); + }; + + virtual SkData* bar(sk_sp obj) { + SkValue* value = obj.release(); + SkData* data = new SkData(); + data->setData(value->data); + return data; + }; + + virtual SkData* qux() { + return new SkData(); + }; +}; + diff --git a/kotlin-native/backend.native/tests/interop/cpp/skia.kt b/kotlin-native/backend.native/tests/interop/cpp/skia.kt index 3892076e17d..81a073760d3 100644 --- a/kotlin-native/backend.native/tests/interop/cpp/skia.kt +++ b/kotlin-native/backend.native/tests/interop/cpp/skia.kt @@ -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() - 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") } diff --git a/kotlin-native/backend.native/tests/interop/cpp/skiaSignature.def b/kotlin-native/backend.native/tests/interop/cpp/skiaSignature.def new file mode 100644 index 00000000000..935f3cb482d --- /dev/null +++ b/kotlin-native/backend.native/tests/interop/cpp/skiaSignature.def @@ -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 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 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; + }; +}; diff --git a/kotlin-native/backend.native/tests/interop/cpp/skiaSignature.kt b/kotlin-native/backend.native/tests/interop/cpp/skiaSignature.kt new file mode 100644 index 00000000000..b2978d1d645 --- /dev/null +++ b/kotlin-native/backend.native/tests/interop/cpp/skiaSignature.kt @@ -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") +} diff --git a/kotlin-native/backend.native/tests/interop/cpp/types.def b/kotlin-native/backend.native/tests/interop/cpp/types.def index 547245199c6..f702069d305 100644 --- a/kotlin-native/backend.native/tests/interop/cpp/types.def +++ b/kotlin-native/backend.native/tests/interop/cpp/types.def @@ -1,5 +1,6 @@ language = C++ compilerOpts = -std=c++14 +plugin = org.jetbrains.kotlin.native.interop.skia package = cpptypes ---