[Kotlin/Native][Interop] Skia interop plugin for cinterop
This commit is contained in:
+40
-22
@@ -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
|
||||
}
|
||||
+7
-6
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+11
@@ -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) =
|
||||
|
||||
+2
@@ -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")
|
||||
|
||||
+33
-14
@@ -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)
|
||||
|
||||
+16
-14
@@ -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()
|
||||
}
|
||||
|
||||
+1
-1
@@ -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)))
|
||||
|
||||
+5
@@ -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")
|
||||
|
||||
|
||||
+2
-1
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-1
@@ -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
|
||||
|
||||
+35
-13
@@ -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.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
@@ -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 {
|
||||
|
||||
+5
@@ -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)
|
||||
}
|
||||
|
||||
+78
-10
@@ -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) }
|
||||
|
||||
+2
-1
@@ -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())
|
||||
|
||||
Reference in New Issue
Block a user