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

This commit is contained in:
Alexander Gorshenev
2020-12-21 03:11:45 +03:00
parent 5f582ad28a
commit 887032667d
62 changed files with 1160 additions and 1155 deletions
@@ -6,48 +6,66 @@ import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.konan.exec.Command
import org.jetbrains.kotlin.konan.file.*
import org.jetbrains.kotlin.konan.target.ClangArgs
import org.jetbrains.kotlin.konan.target.Family
import org.jetbrains.kotlin.konan.target.KonanTarget
private const val dumpBridges = false
class CStubsManager(private val target: KonanTarget) {
fun getUniqueName(prefix: String) = "$prefix${counter++}"
fun addStub(kotlinLocation: CompilerMessageLocation?, lines: List<String>) {
fun addStub(kotlinLocation: CompilerMessageLocation?, lines: List<String>, language: String) {
val stubs = languageToStubs.getOrPut(language) { mutableListOf() }
stubs += Stub(kotlinLocation, lines)
}
fun compile(clang: ClangArgs, messageCollector: MessageCollector, verbose: Boolean): File? {
if (stubs.isEmpty()) return null
fun compile(clang: ClangArgs, messageCollector: MessageCollector, verbose: Boolean): List<File> {
if (languageToStubs.isEmpty()) return emptyList()
val compilerOptions = mutableListOf<String>()
val sourceFileExtension = when {
target.family.isAppleFamily -> {
compilerOptions += "-fobjc-arc"
".m" // TODO: consider managing C and Objective-C stubs separately.
val bitcodes = languageToStubs.entries.map { (language, stubs) ->
val compilerOptions = mutableListOf<String>()
val sourceFileExtension = when {
language == "C++" -> ".cpp"
target.family.isAppleFamily -> {
compilerOptions += "-fobjc-arc"
".m" // TODO: consider managing C and Objective-C stubs separately.
}
else -> ".c"
}
else -> ".c"
}
val cSource = createTempFile("cstubs", sourceFileExtension).deleteOnExit()
cSource.writeLines(stubs.flatMap { it.lines })
val cSource = createTempFile("cstubs", sourceFileExtension).deleteOnExit()
cSource.writeLines(stubs.flatMap { it.lines })
val bitcode = createTempFile("cstubs", ".bc").deleteOnExit()
val bitcode = createTempFile("cstubs", ".bc").deleteOnExit()
val cSourcePath = cSource.absolutePath
val cSourcePath = cSource.absolutePath
val clangCommand = clang.clangC(*compilerOptions.toTypedArray(), "-O2",
cSourcePath, "-emit-llvm", "-c", "-o", bitcode.absolutePath)
val clangCommand = clang.clangC(
*compilerOptions.toTypedArray(), "-O2",
cSourcePath, "-emit-llvm", "-c", "-o", bitcode.absolutePath
)
if (dumpBridges) {
println("CSTUBS for ${language}")
stubs.flatMap { it.lines }.forEach {
println(it)
}
println("CSTUBS in ${cSource.absolutePath}")
println("CSTUBS CLANG COMMAND:")
println(clangCommand.joinToString(" "))
}
val result = Command(clangCommand).getResult(withErrors = true)
if (result.exitCode != 0) {
reportCompilationErrors(cSourcePath, result, messageCollector, verbose)
val result = Command(clangCommand).getResult(withErrors = true)
if (result.exitCode != 0) {
reportCompilationErrors(cSourcePath, stubs, result, messageCollector, verbose)
}
bitcode
}
return bitcode
return bitcodes
}
private fun reportCompilationErrors(
cSourcePath: String,
stubs: List<Stub>,
result: Command.Result,
messageCollector: MessageCollector,
verbose: Boolean
@@ -88,7 +106,7 @@ class CStubsManager(private val target: KonanTarget) {
throw KonanCompilationException()
}
private val stubs = mutableListOf<Stub>()
private val languageToStubs = mutableMapOf<String, MutableList<Stub>>()
private class Stub(val kotlinLocation: CompilerMessageLocation?, val lines: List<String>)
private var counter = 0
}
@@ -8,16 +8,13 @@ import llvm.*
import org.jetbrains.kotlin.backend.common.serialization.KlibIrVersion
import org.jetbrains.kotlin.backend.common.serialization.metadata.KlibMetadataVersion
import org.jetbrains.kotlin.backend.konan.llvm.*
import org.jetbrains.kotlin.backend.konan.llvm.Llvm
import org.jetbrains.kotlin.backend.konan.llvm.objc.linkObjC
import org.jetbrains.kotlin.konan.CURRENT
import org.jetbrains.kotlin.library.KotlinAbiVersion
import org.jetbrains.kotlin.konan.CompilerVersion
import org.jetbrains.kotlin.konan.file.isBitcode
import org.jetbrains.kotlin.library.*
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.konan.target.Family
import org.jetbrains.kotlin.konan.library.impl.buildLibrary
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.library.*
/**
* Supposed to be true for a single LLVM module within final binary.
@@ -48,7 +45,11 @@ val CompilerOutputKind.isCache: Boolean
internal fun produceCStubs(context: Context) {
val llvmModule = context.llvmModule!!
context.cStubsManager.compile(context.config.clang, context.messageCollector, context.inVerbosePhase)?.let {
context.cStubsManager.compile(
context.config.clang,
context.messageCollector,
context.inVerbosePhase
).forEach {
parseAndLinkBitcodeFile(llvmModule, it.absolutePath)
}
}
@@ -116,7 +116,18 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) {
val exportObjCClass = packageScope.getContributedClass("ExportObjCClass")
val CreateNSStringFromKString = packageScope.getContributedFunctions("CreateNSStringFromKString").single()
val nativeHeap = packageScope.getContributedClass("nativeHeap")
val cPointed = packageScope.getContributedClass("CPointed")
val interopGetPtr = packageScope.getContributedVariables("ptr").single {
val singleTypeParameter = it.typeParameters.singleOrNull()
val singleTypeParameterUpperBound = singleTypeParameter?.upperBounds?.singleOrNull()
val extensionReceiverParameter = it.extensionReceiverParameter
singleTypeParameterUpperBound != null &&
extensionReceiverParameter != null &&
TypeUtils.getClassDescriptor(singleTypeParameterUpperBound) == cPointed &&
extensionReceiverParameter.type == singleTypeParameter.defaultType
}.getter!!
}
private fun MemberScope.getContributedVariables(name: String) =
@@ -13,6 +13,8 @@ object RuntimeNames {
val cStructMemberAt = FqName("kotlinx.cinterop.internal.CStruct.MemberAt")
val cStructArrayMemberAt = FqName("kotlinx.cinterop.internal.CStruct.ArrayMemberAt")
val cStructBitField = FqName("kotlinx.cinterop.internal.CStruct.BitField")
val cStruct = FqName("kotlinx.cinterop.internal.CStruct")
val managedType = FqName("kotlinx.cinterop.internal.CStruct.ManagedType")
val objCMethodAnnotation = FqName("kotlinx.cinterop.ObjCMethod")
val objCMethodImp = FqName("kotlinx.cinterop.ObjCMethodImp")
val independent = FqName("kotlin.native.internal.Independent")
@@ -8,12 +8,9 @@ import org.jetbrains.kotlin.backend.common.lower.at
import org.jetbrains.kotlin.backend.common.lower.irNot
import org.jetbrains.kotlin.backend.konan.PrimitiveBinaryType
import org.jetbrains.kotlin.backend.konan.RuntimeNames
import org.jetbrains.kotlin.backend.konan.ir.konanLibrary
import org.jetbrains.kotlin.backend.konan.getObjCMethodInfo
import org.jetbrains.kotlin.backend.konan.ir.*
import org.jetbrains.kotlin.backend.konan.ir.KonanSymbols
import org.jetbrains.kotlin.backend.konan.ir.buildSimpleAnnotation
import org.jetbrains.kotlin.backend.konan.ir.getAnnotationArgumentValue
import org.jetbrains.kotlin.backend.konan.ir.typeWithStarProjections
import org.jetbrains.kotlin.backend.konan.isObjCMetaClass
import org.jetbrains.kotlin.backend.konan.lower.FunctionReferenceLowering
import org.jetbrains.kotlin.descriptors.ClassKind
@@ -50,6 +47,7 @@ internal interface KotlinStubs {
val irBuiltIns: IrBuiltIns
val symbols: KonanSymbols
val target: KonanTarget
val language: String
fun addKotlin(declaration: IrDeclaration)
fun addC(lines: List<String>)
fun getUniqueCName(prefix: String): String
@@ -111,10 +109,10 @@ private fun KotlinToCCallBuilder.buildKotlinBridgeCall(transformCall: (IrMemberA
transformCall
)
private fun IrType.isManagedType(): Boolean= this.classOrNull?.owner?.hasAnnotation(RuntimeNames.managedType) ?: false
internal fun KotlinStubs.generateCCall(expression: IrCall, builder: IrBuilderWithScope, isInvoke: Boolean,
foreignExceptionMode: ForeignExceptionMode.Mode = ForeignExceptionMode.default): IrExpression {
require(expression.dispatchReceiver == null) { renderCompilerError(expression) }
val callBuilder = KotlinToCCallBuilder(builder, this, isObjCMethod = false, foreignExceptionMode)
val callee = expression.symbol.owner
@@ -125,6 +123,7 @@ internal fun KotlinStubs.generateCCall(expression: IrCall, builder: IrBuilderWit
val targetFunctionName: String
if (isInvoke) {
require(expression.dispatchReceiver == null) { renderCompilerError(expression) }
targetPtrParameter = callBuilder.passThroughBridge(
expression.extensionReceiver!!,
symbols.interopCPointer.typeWithStarProjections,
@@ -148,7 +147,14 @@ internal fun KotlinStubs.generateCCall(expression: IrCall, builder: IrBuilderWit
val arguments = (0 until expression.valueArgumentsCount).map {
expression.getValueArgument(it)
}
callBuilder.addArguments(arguments, callee)
val receiverParameter = expression.symbol.owner.dispatchReceiverParameter
val self: List<IrExpression> = when {
receiverParameter == null -> emptyList()
receiverParameter.type.classOrNull?.owner?.isCompanion == true -> emptyList()
else -> listOf(expression.dispatchReceiver!!)
}
callBuilder.addArguments(self + arguments, callee)
}
val returnValuePassing = if (isInvoke) {
@@ -176,7 +182,13 @@ internal fun KotlinStubs.generateCCall(expression: IrCall, builder: IrBuilderWit
private fun KotlinToCCallBuilder.addArguments(arguments: List<IrExpression?>, callee: IrFunction) {
arguments.forEachIndexed { index, argument ->
val parameter = callee.valueParameters[index]
val parameter = if (callee.dispatchReceiverParameter != null &&
(callee.dispatchReceiverParameter?.type?.isManagedType() == true)) {
if (index == 0) callee.dispatchReceiverParameter!! else callee.valueParameters[index-1]
} else {
callee.valueParameters[index]
}
if (parameter.isVararg) {
require(index == arguments.lastIndex) { stubs.renderCompilerError(argument) }
addVariadicArguments(argument)
@@ -472,7 +484,7 @@ private fun CCallbackBuilder.buildCFunction(): String {
val cLines = mutableListOf<String>()
cLines += "${cFunctionBuilder.buildSignature(result)} {"
cLines += "${cFunctionBuilder.buildSignature(result, stubs.language)} {"
cLines += cBodyLines
cLines += "}"
@@ -724,12 +736,18 @@ private fun KotlinStubs.mapType(
val cStructType = getNamedCStructType(kotlinClass)
require(cStructType != null) { renderCompilerError(location) }
StructValuePassing(kotlinClass, cStructType)
if (type.isManagedType()) {
// TODO: this should probably be better abstracted in a plugin.
// For Skia plugin we release sk_sp on the C++ side passing just the raw pointer.
// So managed by value is handled as voidPtr here for now.
TrivialValuePassing(type, CTypes.voidPtr)
} else {
StructValuePassing(kotlinClass, cStructType)
}
}
type.classOrNull?.isSubtypeOfClass(symbols.nativePointed) == true -> {
type.classOrNull?.isSubtypeOfClass(symbols.nativePointed) == true ->
TrivialValuePassing(type, CTypes.voidPtr)
}
type.isFunction() -> {
require(!variadic) { renderCompilerError(location) }
@@ -738,7 +756,7 @@ private fun KotlinStubs.mapType(
type.isObjCReferenceType(target, irBuiltIns) -> ObjCReferenceValuePassing(symbols, type, retained = retained)
else -> throwCompilerError(location, "doesn't correspond to any C type")
else -> throwCompilerError(location, "doesn't correspond to any C type: ${type.render()}")
}
private class CExpression(val expression: String, val type: CType)
@@ -883,6 +901,7 @@ private class StructValuePassing(private val kotlinClass: IrClass, override val
bridgeCallBuilder.prepare += kotlinPointed
val cPointer = passThroughBridge(irGet(kotlinPointed), kotlinPointedType, CTypes.pointer(cType))
cBridgeBodyLines += "*${cPointer.name} = $expression;"
buildKotlinBridgeCall {
@@ -1258,7 +1277,7 @@ private class ObjCBlockPointerValuePassing(
val block = buildString {
append('^')
append(callbackBuilder.cFunctionBuilder.buildSignature(""))
append(callbackBuilder.cFunctionBuilder.buildSignature("", stubs.language))
append(" { ")
callbackBuilder.cBodyLines.forEach {
append(it)
@@ -22,7 +22,7 @@ import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl
import org.jetbrains.kotlin.ir.symbols.impl.IrValueParameterSymbolImpl
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.impl.IrUninitializedType
import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.util.irBuilder
import org.jetbrains.kotlin.ir.util.irCatch
import org.jetbrains.kotlin.konan.ForeignExceptionMode
@@ -51,17 +51,19 @@ internal class CFunctionBuilder {
fun getType(): CType = CTypes.function(returnType, parameters.map { it.type }, variadic)
fun buildSignature(name: String): String = returnType.render(buildString {
append(name)
append('(')
parameters.joinTo(this)
if (parameters.isEmpty()) {
if (!variadic) append("void")
} else {
if (variadic) append(", ...")
}
append(')')
})
fun buildSignature(name: String, language: String): String =
(if (language == "C++") "extern \"C\" const " else "") +
returnType.render(buildString {
append(name)
append('(')
parameters.joinTo(this)
if (parameters.isEmpty()) {
if (!variadic) append("void")
} else {
if (variadic) append(", ...")
}
append(')')
})
}
@@ -145,7 +147,7 @@ internal class KotlinCBridgeBuilder(
startOffset: Int,
endOffset: Int,
cName: String,
stubs: KotlinStubs,
val stubs: KotlinStubs,
isKotlinToC: Boolean,
foreignExceptionMode: ForeignExceptionMode.Mode = ForeignExceptionMode.default
) {
@@ -163,7 +165,7 @@ internal class KotlinCBridgeBuilder(
cBridgeBuilder.setReturnType(cReturnType)
}
fun buildCSignature(name: String): String = cBridgeBuilder.buildSignature(name)
fun buildCSignature(name: String): String = cBridgeBuilder.buildSignature(name, stubs.language)
fun buildKotlinBridge() = kotlinBridgeBuilder.build()
}
@@ -33,7 +33,7 @@ internal fun IrType.isCEnumType(): Boolean {
private val cCall = RuntimeNames.cCall
// Make sure external stubs always get proper annotaions.
private fun IrDeclaration.hasCCallAnnotation(name: String): Boolean =
fun IrDeclaration.hasCCallAnnotation(name: String): Boolean =
this.annotations.hasAnnotation(cCall.child(Name.identifier(name)))
// LazyIr doesn't pass annotations from descriptor to IrValueParameter.
|| this.descriptor.annotations.hasAnnotation(cCall.child(Name.identifier(name)))
@@ -63,6 +63,7 @@ internal class KonanSymbols(
val nativePointed = symbolTable.referenceClass(context.interopBuiltIns.nativePointed)
val nativePtrType = nativePtr.typeWith(arguments = emptyList())
val nonNullNativePtr = symbolTable.referenceClass(context.nonNullNativePtr)
val nonNullNativePtrType = nonNullNativePtr.typeWith(arguments = emptyList())
val immutableBlobOf = symbolTable.referenceSimpleFunction(context.immutableBlobOf)
@@ -205,6 +206,10 @@ internal class KonanSymbols(
val nativeMemUtils = symbolTable.referenceClass(context.interopBuiltIns.nativeMemUtils)
val nativeHeap = symbolTable.referenceClass(context.interopBuiltIns.nativeHeap)
val interopGetPtr = symbolTable.referenceSimpleFunction(context.interopBuiltIns.interopGetPtr)
val readBits = interopFunction("readBits")
val writeBits = interopFunction("writeBits")
@@ -148,7 +148,8 @@ internal interface DescriptorToIrTranslationMixin {
private fun IrDeclaration.generateAnnotations() {
annotations += descriptor.annotations.map {
typeTranslator.constantValueGenerator.generateAnnotationConstructorCall(it)!!
typeTranslator.constantValueGenerator.generateAnnotationConstructorCall(it)
?: error("Could not generate annotations for $it")
}
}
}
@@ -54,7 +54,7 @@ internal class IrProviderForCEnumAndCStructStubs(
private val cStructCompanionGenerator =
CStructVarCompanionGenerator(context, interopBuiltIns)
private val cStructClassGenerator =
CStructVarClassGenerator(context, interopBuiltIns, cStructCompanionGenerator)
CStructVarClassGenerator(context, interopBuiltIns, cStructCompanionGenerator, symbols)
fun isCEnumOrCStruct(declarationDescriptor: DeclarationDescriptor): Boolean =
declarationDescriptor.run { findCEnumDescriptor(interopBuiltIns) ?: findCStructDescriptor(interopBuiltIns) } != null
@@ -92,6 +92,10 @@ internal class IrProviderForCEnumAndCStructStubs(
fun getDeclaration(descriptor: DeclarationDescriptor, idSignature: IdSignature, file: IrFile, symbolKind: BinarySymbolData.SymbolKind): IrSymbolOwner {
return symbolTable.run {
when (symbolKind) {
BinarySymbolData.SymbolKind.CONSTRUCTOR_SYMBOL -> declareConstructorFromLinker(descriptor as ClassConstructorDescriptor, idSignature) { s ->
generateIrIfNeeded(s, file)
s.owner
}
BinarySymbolData.SymbolKind.CLASS_SYMBOL -> declareClassFromLinker(descriptor as ClassDescriptor, idSignature) { s ->
generateIrIfNeeded(s, file)
s.owner
@@ -5,17 +5,15 @@
package org.jetbrains.kotlin.backend.konan.ir.interop.cstruct
import org.jetbrains.kotlin.backend.konan.InteropBuiltIns
import org.jetbrains.kotlin.backend.konan.ir.KonanSymbols
import org.jetbrains.kotlin.backend.konan.ir.interop.DescriptorToIrTranslationMixin
import org.jetbrains.kotlin.backend.konan.ir.interop.irInstanceInitializer
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.ir.builders.irBlockBody
import org.jetbrains.kotlin.ir.builders.irCall
import org.jetbrains.kotlin.ir.builders.irGet
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrConstructor
import org.jetbrains.kotlin.ir.declarations.IrDeclarationContainer
import org.jetbrains.kotlin.ir.declarations.addMember
import org.jetbrains.kotlin.ir.builders.irGetObject
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
import org.jetbrains.kotlin.ir.expressions.impl.IrDelegatingConstructorCallImpl
import org.jetbrains.kotlin.ir.util.*
@@ -24,7 +22,8 @@ import org.jetbrains.kotlin.psi2ir.generators.GeneratorContext
internal class CStructVarClassGenerator(
context: GeneratorContext,
private val interopBuiltIns: InteropBuiltIns,
private val companionGenerator: CStructVarCompanionGenerator
private val companionGenerator: CStructVarCompanionGenerator,
private val symbols: KonanSymbols
) : DescriptorToIrTranslationMixin {
override val irBuiltIns: IrBuiltIns = context.irBuiltIns
@@ -48,12 +47,25 @@ internal class CStructVarClassGenerator(
createClass(descriptor) { irClass ->
irClass.addMember(createPrimaryConstructor(irClass))
irClass.addMember(companionGenerator.generate(descriptor))
descriptor.constructors
.filterNot { it.isPrimary }
.map {
val constructor = createSecondaryConstructor(it)
irClass.addMember(constructor)
}
descriptor.unsubstitutedMemberScope
.getContributedDescriptors()
.filterIsInstance<PropertyDescriptor>()
.filter { it.kind != CallableMemberDescriptor.Kind.FAKE_OVERRIDE }
.map(this::createProperty)
.forEach(irClass::addMember)
.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)
}
private fun createPrimaryConstructor(irClass: IrClass): IrConstructor {
@@ -74,4 +86,14 @@ internal class CStructVarClassGenerator(
}
}
}
private fun createSecondaryConstructor(descriptor: ClassConstructorDescriptor): IrConstructor {
return createConstructor(descriptor).also {
postLinkageSteps.add {
it.body = irBuilder(irBuiltIns, it.symbol, SYNTHETIC_OFFSET, SYNTHETIC_OFFSET).irBlockBody {
// Empty. The real body is constructed at the call site by the interop lowering phase.
}
}
}
}
}
@@ -8,12 +8,16 @@ import org.jetbrains.kotlin.backend.konan.InteropBuiltIns
import org.jetbrains.kotlin.backend.konan.descriptors.getArgumentValueOrNull
import org.jetbrains.kotlin.backend.konan.ir.interop.DescriptorToIrTranslationMixin
import org.jetbrains.kotlin.backend.konan.ir.interop.irInstanceInitializer
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
import org.jetbrains.kotlin.ir.builders.irBlockBody
import org.jetbrains.kotlin.ir.builders.irInt
import org.jetbrains.kotlin.ir.builders.irLong
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrConstructor
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.addMember
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
import org.jetbrains.kotlin.ir.expressions.impl.IrDelegatingConstructorCallImpl
@@ -41,6 +45,19 @@ internal class CStructVarCompanionGenerator(
val size = annotation.getArgumentValueOrNull<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
}
}
.forEach(companionIrClass::addMember)
}
private fun createCompanionConstructor(companionObjectDescriptor: ClassDescriptor, size: Long, align: Int): IrConstructor {
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.backend.common.atMostOne
import org.jetbrains.kotlin.backend.common.ir.copyTo
import org.jetbrains.kotlin.backend.common.lower.*
import org.jetbrains.kotlin.backend.konan.*
import org.jetbrains.kotlin.backend.konan.cgen.hasCCallAnnotation
import org.jetbrains.kotlin.backend.konan.ir.*
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
@@ -297,6 +298,10 @@ private class InlineClassTransformer(private val context: Context) : IrBuildingT
if (declaration.constructedClass.isNativePrimitiveType()) {
// Constructors for these types aren't used and actually are malformed (e.g. lack the parameter).
// Skipping here for simplicity.
} else if (declaration.hasCCallAnnotation("CppClassConstructor") && !declaration.isPrimary) {
// At this point secondary cpp constructor calls have already been transformed
// by interop lowering. So don't mess with them.
// Otherwise we could assert having assumptions on (empty at the moment) body of the constructor.
} else {
buildLoweredConstructor(declaration)
}
@@ -6,10 +6,7 @@
package org.jetbrains.kotlin.backend.konan.lower
import org.jetbrains.kotlin.backend.common.*
import org.jetbrains.kotlin.backend.common.ir.allParameters
import org.jetbrains.kotlin.backend.common.ir.copyTo
import org.jetbrains.kotlin.backend.common.ir.createDispatchReceiverParameter
import org.jetbrains.kotlin.backend.common.ir.simpleFunctions
import org.jetbrains.kotlin.backend.common.ir.*
import org.jetbrains.kotlin.backend.common.lower.*
import org.jetbrains.kotlin.backend.konan.*
import org.jetbrains.kotlin.backend.konan.cgen.*
@@ -17,6 +14,7 @@ import org.jetbrains.kotlin.backend.konan.descriptors.allOverriddenFunctions
import org.jetbrains.kotlin.backend.konan.descriptors.synthesizedName
import org.jetbrains.kotlin.backend.konan.ir.*
import org.jetbrains.kotlin.backend.konan.ir.companionObject
import org.jetbrains.kotlin.backend.konan.ir.isFinalClass
import org.jetbrains.kotlin.backend.konan.llvm.IntrinsicType
import org.jetbrains.kotlin.backend.konan.llvm.tryGetIntrinsicType
import org.jetbrains.kotlin.backend.konan.serialization.resolveFakeOverrideMaybeAbstract
@@ -45,6 +43,7 @@ import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.konan.ForeignExceptionMode
import org.jetbrains.kotlin.konan.library.KonanLibrary
internal class InteropLowering(context: Context) : FileLoweringPass {
// TODO: merge these lowerings.
@@ -83,12 +82,19 @@ private abstract class BaseInteropIrTransformer(private val context: Context) :
override val irBuiltIns get() = context.irBuiltIns
override val symbols get() = context.ir.symbols
val klib: KonanLibrary? get() {
return (element as? IrCall)?.symbol?.owner?.konanLibrary as? KonanLibrary
}
override val language: String
get() = klib?.manifestProperties?.getProperty("language") ?: "C"
override fun addKotlin(declaration: IrDeclaration) {
addTopLevel(declaration)
}
override fun addC(lines: List<String>) {
context.cStubsManager.addStub(location, lines)
context.cStubsManager.addStub(location, lines, language)
}
override fun getUniqueCName(prefix: String) =
@@ -763,6 +769,10 @@ private class InteropTransformer(val context: Context, override val irFile: IrFi
override fun visitConstructorCall(expression: IrConstructorCall): IrExpression {
expression.transformChildrenVoid(this)
if (expression.symbol.owner.hasCCallAnnotation("CppClassConstructor")) {
return transformSecondaryCppConstructorCall(expression)
}
val callee = expression.symbol.owner
val inlinedClass = callee.returnType.getInlinedClassNative()
require(inlinedClass?.descriptor != interop.cPointer) { renderCompilerError(expression) }
@@ -793,6 +803,58 @@ private class InteropTransformer(val context: Context, override val irFile: IrFi
}
}
private fun transformSecondaryCppConstructorCall(expression: IrConstructorCall): IrExpression {
val irConstructor = expression.symbol.owner
val irClass = irConstructor.constructedClass
val primaryConstructor = irClass.primaryConstructor!!.symbol
// TODO: don't use it is deprecated.
val alloc = symbols.interopAllocType
val nativeHeap = symbols.nativeHeap
val interopGetPtr = symbols.interopGetPtr
val correspondingInit = irClass.companionObject()!!
.declarations
.filterIsInstance<IrSimpleFunction>()
.filter { it.name.toString() == "__init__"}
.filter { it.valueParameters.size == irConstructor.valueParameters.size + 1}
.single {
it.valueParameters.drop(1).mapIndexed() { index, initParameter ->
initParameter.type == irConstructor.valueParameters[index].type
}.all{ it }
}
val irBlock = builder.at(expression)
.irBlock {
val call = irCall(primaryConstructor).also {
val nativePointed = irCall(alloc).apply {
extensionReceiver = irGetObject(nativeHeap)
putValueArgument(0, irGetObject(irClass.companionObject()!!.symbol))
}
val nativePtr = irCall(symbols.interopNativePointedGetRawPointer).apply {
extensionReceiver = nativePointed
}
it.putValueArgument(0, nativePtr)
}
val tmp = irTemporary(call)
val initCall = irCall(correspondingInit.symbol).apply {
putValueArgument(0,
irCall(interopGetPtr).apply {
extensionReceiver = irGet(tmp)
}
)
for (index in 0 until expression.valueArgumentsCount) {
putValueArgument(index+1, expression.getValueArgument(index)!!)
}
}
val initCCall = generateCCall(initCall)
+initCCall
+irGet(tmp)
}
return irBlock
}
/**
* Handle `const val`s that come from interop libraries.
*
@@ -817,6 +879,16 @@ private class InteropTransformer(val context: Context, override val irFile: IrFi
return initializer.shallowCopy()
}
private fun generateCCall(expression: IrCall): IrExpression {
val function = expression.symbol.owner
context.llvmImports.add(function.llvmSymbolOrigin)
val exceptionMode = ForeignExceptionMode.byValue(
function.konanLibrary?.manifestProperties?.getProperty(ForeignExceptionMode.manifestKey)
)
return generateWithStubs(expression) { generateCCall(expression, builder, isInvoke = false, exceptionMode) }
}
override fun visitCall(expression: IrCall): IrExpression {
val intrinsicType = tryGetIntrinsicType(expression)
if (intrinsicType == IntrinsicType.OBJC_INIT_BY) {
@@ -858,11 +930,7 @@ private class InteropTransformer(val context: Context, override val irFile: IrFi
}
if (function.annotations.hasAnnotation(RuntimeNames.cCall)) {
context.llvmImports.add(function.llvmSymbolOrigin)
val exceptionMode = ForeignExceptionMode.byValue(
function.konanLibrary?.manifestProperties?.getProperty(ForeignExceptionMode.manifestKey)
)
return generateWithStubs { generateCCall(expression, builder, isInvoke = false, exceptionMode) }
return generateCCall(expression)
}
val failCompilation = { msg: String -> error(irFile, expression, msg) }
@@ -322,7 +322,8 @@ private class BackendChecker(val context: Context, val irFile: IrFile) : IrEleme
)
}
if (callee.returnType.isNativePointed(symbols))
if (callee.returnType.isNativePointed(symbols) &&
!callee.hasCCallAnnotation("CppClassConstructor"))
reportError(expression, "Native interop types constructors must not be called directly")
}
@@ -57,6 +57,7 @@ externalStdlibTestsDir.mkdirs()
ext.platformManager = project.project(":kotlin-native").platformManager
ext.target = platformManager.targetManager(project.testTarget).target
ext.llvmHome = platformManager.platform(target).configurables.absoluteLlvmHome
ext.testLibraryDir = "${ext.testOutputRoot}/klib/platform/${project.target.name}"
@@ -3824,13 +3825,29 @@ createInterop("leakMemoryWithRunningThread") {
}
createInterop("cppClass") {
if (isAppleTarget(project)) {
// TODO: For cpp we need a header with `new`.
it.extraOpts "-compiler-option", "-I$llvmHome/include/c++/v1"
}
it.defFile 'interop/cpp/cppClass.def'
}
createInterop("cppTypes") {
if (isAppleTarget(project)) {
// TODO: For cpp we need a header with `new`.
it.extraOpts "-compiler-option", "-I$llvmHome/include/c++/v1"
}
it.defFile 'interop/cpp/types.def'
}
createInterop("cppSkia") {
if (PlatformInfo.isMac()) {
// TODO: For cpp we need a header with `new`.
it.extraOpts "-compiler-option", "-I$llvmHome/include/c++/v1"
}
it.defFile 'interop/cpp/skia.def'
}
if (PlatformInfo.isAppleTarget(project)) {
createInterop("objcSmoke") {
it.defFile 'interop/objc/objcSmoke.def'
@@ -4202,6 +4219,13 @@ interopTest("interop_cppTypes") {
interop = 'cppTypes'
}
interopTest("interop_cppSkia") {
disabled = (project.testTarget == 'wasm32') // No interop for wasm yet.
source = "interop/cpp/skia.kt"
interop = 'cppSkia'
goldValue = "17 17\n"
}
/*
TODO: This test isn't run automatically
task interop_echo_server(type: RunInteropKonanTest) {
@@ -85,10 +85,12 @@ fun main() {
val testRun = FeatureTest()
testRun.ctorDefault()
testRun.ctorWithParam()
val a0 = retByValue(null)
println("a0.useContents {iPub} = ${a0.useContents {iPub}}" )
println("a0.useContents { foo() } = ${a0.useContents { foo() }}" )
// By value for C++ requires further design of stubs mechanism.
// So not supported for now.
//val a0 = retByValue(null)
//println("a0.useContents {iPub} = ${a0.useContents {iPub}}" )
//println("a0.useContents { foo() } = ${a0.useContents { foo() }}" )
// retByValue(null)!!.getValue().foo()
// val a1 = interpretPointed<CppTest>(retByValue(null).rawValue)
@@ -0,0 +1,41 @@
language = C++
compilerOpts = -std=c++17
plugin = org.jetbrains.kotlin.native.interop.skia
---
// TODO: this one checks the syntactic aspect for now.
// To be updated for proper c++ destructor and
// kotlin garbage collection interaction.
template <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 Value {
public:
int data;
};
class Foo {
public:
Foo() { }
virtual sk_sp<Value> foo(Value *obj) {
return sk_sp<Value>(obj);
}
virtual Value* bar(sk_sp<Value> obj) {
return obj.release();
}
};
@@ -0,0 +1,13 @@
import kotlinx.cinterop.*
import kotlin.test.*
import skia.*
fun main() {
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}")
}
@@ -3,7 +3,7 @@ import kotlin.test.*
import kotlin.random.*
import cpptypes.*
/*
@Test
fun test_retByValue(k: Int) {
memScoped {
@@ -11,6 +11,7 @@ fun test_retByValue(k: Int) {
assertEquals(k, x.get())
}
}
*/
@Test
fun test_retByPtr(k: Int) {
@@ -35,7 +36,7 @@ fun test_retByRefConst(k: Int) {
val x = interpretPointed<CppTest>(retByRefConst(k).rawValue)
assertEquals(k, x.get())
}
/*
@Test
fun test_paramByValue(k: Int) {
val x = nativeHeap.alloc<CppTest>() {}
@@ -43,7 +44,7 @@ fun test_paramByValue(k: Int) {
assertEquals(k, paramByValue(x.readValue()))
nativeHeap.free(x)
}
*/
@Test
fun test_paramByPtr(k: Int) {
val x = nativeHeap.alloc<CppTest>() {}
@@ -80,12 +81,12 @@ fun main() {
val seed = Random.nextInt()
val r = Random(seed)
test_retByValue(r.nextInt())
//test_retByValue(r.nextInt())
test_retByPtr(r.nextInt())
test_retByPtrConst(r.nextInt())
test_retByRef(r.nextInt())
test_retByRefConst(r.nextInt())
test_paramByValue(r.nextInt())
//test_paramByValue(r.nextInt())
test_paramByPtr(r.nextInt())
test_paramByPtrConst(r.nextInt())
test_paramByRef(r.nextInt())