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
@@ -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
---