diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CAdapterGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CAdapterGenerator.kt index bd348c83026..7402dfc0ffe 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CAdapterGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CAdapterGenerator.kt @@ -12,8 +12,6 @@ import org.jetbrains.kotlin.backend.common.descriptors.allParameters import org.jetbrains.kotlin.backend.common.descriptors.explicitParameters import org.jetbrains.kotlin.backend.common.pop import org.jetbrains.kotlin.backend.common.push -import org.jetbrains.kotlin.backend.konan.descriptors.isNothing -import org.jetbrains.kotlin.backend.konan.descriptors.isUnit import org.jetbrains.kotlin.backend.konan.llvm.* import org.jetbrains.kotlin.builtins.UnsignedType import org.jetbrains.kotlin.descriptors.* @@ -31,7 +29,9 @@ import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.descriptorUtil.* import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.TypeUtils +import org.jetbrains.kotlin.types.typeUtil.isNothing import org.jetbrains.kotlin.types.typeUtil.isUnit +import org.jetbrains.kotlin.types.typeUtil.makeNullable private enum class ScopeKind { TOP, @@ -63,6 +63,13 @@ private operator fun String.times(count: Int): String { return builder.toString() } +private val KotlinType.shortNameForPredefinedType + get() = this.toString().split('.').last() + + +private val KotlinType.createNullableNameForPredefinedType + get() = "createNullable${this.shortNameForPredefinedType}" + internal val cKeywords = setOf( // Actual C keywords. "auto", "break", "case", @@ -104,10 +111,9 @@ internal val cKeywords = setOf( "xor_eq" ) -private fun org.jetbrains.kotlin.types.KotlinType.isGeneric() = +private fun KotlinType.isGeneric() = constructor.declarationDescriptor is TypeParameterDescriptor - private fun isExportedFunction(descriptor: FunctionDescriptor): Boolean { if (!descriptor.isEffectivelyPublicApi || !descriptor.kind.isReal || descriptor.isExpect) return false @@ -147,6 +153,8 @@ private fun functionImplName(descriptor: DeclarationDescriptor, default: String, return value.takeIf { value != null && value.isNotEmpty() } ?: default } +internal data class SignatureElement(val name: String, val type: KotlinType) + private class ExportedElementScope(val kind: ScopeKind, val name: String) { val elements = mutableListOf() val scopes = mutableListOf() @@ -198,7 +206,7 @@ private class ExportedElement(val kind: ElementKind, lateinit var cname: String override fun toString(): String { - return "$kind: $name (aliased to $cname)" + return "$kind: $name (aliased to ${if (::cname.isInitialized) cname.toString() else ""})" } override val context = owner.context @@ -283,22 +291,22 @@ private class ExportedElement(val kind: ElementKind, fun KotlinType.includeToSignature() = !this.isUnit() - fun makeCFunctionSignature(shortName: Boolean): List> { + fun makeCFunctionSignature(shortName: Boolean): List { if (!isFunction) { throw Error("only for functions") } val descriptor = declaration val original = descriptor.original as FunctionDescriptor val returned = when { - original is ConstructorDescriptor -> uniqueName(original, shortName) to original.constructedClass - else -> uniqueName(original, shortName) to TypeUtils.getClassDescriptor(original.returnType!!)!! + original is ConstructorDescriptor -> + SignatureElement(uniqueName(original, shortName), original.constructedClass.defaultType) + else -> + SignatureElement(uniqueName(original, shortName), original.returnType!!) } val uniqueNames = owner.paramsToUniqueNames(original.explicitParameters) val params = ArrayList(original.explicitParameters .filter { it.type.includeToSignature() } - .map { it -> - uniqueNames[it]!! to TypeUtils.getClassDescriptor(it.type)!! - }) + .map { SignatureElement(uniqueNames[it]!!, it.type) }) return listOf(returned) + params } @@ -312,32 +320,30 @@ private class ExportedElement(val kind: ElementKind, original is ConstructorDescriptor -> owner.context.builtIns.unitType else -> original.returnType!! } - val returnedClass = TypeUtils.getClassDescriptor(returnedType)!! val params = ArrayList(original.allParameters .filter { it.type.includeToSignature() } .map { - owner.translateTypeBridge(TypeUtils.getClassDescriptor(it.type)!!) + owner.translateTypeBridge(it.type) }) - if (owner.isMappedToReference(returnedClass) || owner.isMappedToString(returnedClass)) { + if (owner.isMappedToReference(returnedType) || owner.isMappedToString(returnedType)) { params += "KObjHeader**" } - return listOf(owner.translateTypeBridge(returnedClass)) + params + return listOf(owner.translateTypeBridge(returnedType)) + params } fun makeFunctionPointerString(): String { val signature = makeCFunctionSignature(true) - return "${owner.translateType(signature[0].second)} (*${signature[0].first})(${signature.drop(1).map { it -> "${owner.translateType(it.second)} ${it.first}" }.joinToString(", ")});" + return "${owner.translateType(signature[0])} (*${signature[0].name})(${signature.drop(1).map { it -> "${owner.translateType(it)} ${it.name}" }.joinToString(", ")});" } fun makeTopLevelFunctionString(): Pair { val signature = makeCFunctionSignature(false) - val name = signature[0].first + val name = signature[0].name return (name to - "extern ${owner.translateType(signature[0].second)} $name(${signature.drop(1).map { it -> "${owner.translateType(it.second)} ${it.first}" }.joinToString(", ")});") + "extern ${owner.translateType(signature[0])} $name(${signature.drop(1).map { it -> "${owner.translateType(it)} ${it.name}" }.joinToString(", ")});") } - fun makeFunctionDeclaration(): String { assert(isFunction) val bridge = makeBridgeSignature() @@ -355,7 +361,7 @@ private class ExportedElement(val kind: ElementKind, assert(isClass) val typeGetter = "extern \"C\" ${owner.prefix}_KType* ${cname}_type(void);" val instanceGetter = if (isSingletonObject) { - val objectClassC = owner.translateType(declaration as ClassDescriptor) + val objectClassC = owner.translateType((declaration as ClassDescriptor).defaultType) """ | |extern "C" KObjHeader* ${cname}_instance(KObjHeader**); @@ -373,7 +379,7 @@ private class ExportedElement(val kind: ElementKind, fun makeEnumEntryDeclaration(): String { assert(isEnumEntry) val enumClass = declaration.containingDeclaration as ClassDescriptor - val enumClassC = owner.translateType(enumClass) + val enumClassC = owner.translateType(enumClass.defaultType) return """ |extern "C" KObjHeader* $cname(KObjHeader**); @@ -386,26 +392,26 @@ private class ExportedElement(val kind: ElementKind, """.trimMargin() } - private fun translateArgument(name: String, clazz: ClassDescriptor, direction: Direction, - builder: StringBuilder): String { + private fun translateArgument(name: String, signatureElement: SignatureElement, + direction: Direction, builder: StringBuilder): String { return when { - owner.isMappedToString(clazz) -> + owner.isMappedToString(signatureElement.type) -> if (direction == Direction.C_TO_KOTLIN) { builder.append(" KObjHolder ${name}_holder;\n") "CreateStringFromCString($name, ${name}_holder.slot())" } else { "CreateCStringFromString($name)" } - owner.isMappedToReference(clazz) -> + owner.isMappedToReference(signatureElement.type) -> if (direction == Direction.C_TO_KOTLIN) { builder.append(" KObjHolder ${name}_holder2;\n") "DerefStablePointer(${name}.pinned, ${name}_holder2.slot())" } else { - "((${owner.translateType(clazz)}){ .pinned = CreateStablePointer(${name})})" + "((${owner.translateType(signatureElement.type)}){ .pinned = CreateStablePointer(${name})})" } else -> { - assert(!clazz.defaultType.binaryTypeIsReference()) { - println(clazz.toString()) + assert(!signatureElement.type.binaryTypeIsReference()) { + println(signatureElement.toString()) } name } @@ -418,17 +424,18 @@ private class ExportedElement(val kind: ElementKind, else "${cname}_impl" - private fun translateBody(cfunction: List>): String { + private fun translateBody(cfunction: List): String { val visibility = if (isTopLevelFunction) "RUNTIME_USED extern \"C\"" else "static" val builder = StringBuilder() - builder.append("$visibility ${owner.translateType(cfunction[0].second)} ${cnameImpl}(${cfunction.drop(1).mapIndexed { index, it -> "${owner.translateType(it.second)} arg${index}" }.joinToString(", ")}) {\n") + builder.append("$visibility ${owner.translateType(cfunction[0])} ${cnameImpl}(${cfunction.drop(1). + mapIndexed { index, it -> "${owner.translateType(it)} arg${index}" }.joinToString(", ")}) {\n") val args = ArrayList(cfunction.drop(1).mapIndexed { index, pair -> - translateArgument("arg$index", pair.second, Direction.C_TO_KOTLIN, builder) + translateArgument("arg$index", pair, Direction.C_TO_KOTLIN, builder) }) - val isVoidReturned = owner.isMappedToVoid(cfunction[0].second) + val isVoidReturned = owner.isMappedToVoid(cfunction[0].type) val isConstructor = declaration is ConstructorDescriptor - val isObjectReturned = !isConstructor && owner.isMappedToReference(cfunction[0].second) - val isStringReturned = owner.isMappedToString(cfunction[0].second) + val isObjectReturned = !isConstructor && owner.isMappedToReference(cfunction[0].type) + val isStringReturned = owner.isMappedToString(cfunction[0].type) // TODO: do we really need that in every function? builder.append(" Kotlin_initRuntimeIfNeeded();\n") builder.append(" try {\n") @@ -452,7 +459,7 @@ private class ExportedElement(val kind: ElementKind, if (!isVoidReturned) { val result = translateArgument( - "result", cfunction[0].second, Direction.KOTLIN_TO_C, builder) + "result", cfunction[0], Direction.KOTLIN_TO_C, builder) builder.append(" return $result;\n") } builder.append(" } catch (ExceptionObjHolder& e) { TerminateWithUnhandledException(e.obj()); } \n") @@ -462,7 +469,7 @@ private class ExportedElement(val kind: ElementKind, return builder.toString() } - private fun addUsedType(type: org.jetbrains.kotlin.types.KotlinType, set: MutableSet) { + private fun addUsedType(type: KotlinType, set: MutableSet) { if (type.constructor.declarationDescriptor is TypeParameterDescriptor) return val clazz = TypeUtils.getClassDescriptor(type) if (clazz == null) { @@ -521,6 +528,13 @@ internal class CAdapterGenerator(val context: Context) : DeclarationDescriptorVi private var symbolTableOrNull: SymbolTable? = null internal val symbolTable get() = symbolTableOrNull!! + private val predefinedTypes = listOf( + context.builtIns.byteType, context.builtIns.shortType, + context.builtIns.intType, context.builtIns.longType, + context.builtIns.floatType, context.builtIns.doubleType, + context.builtIns.charType, context.builtIns.booleanType, + context.builtIns.unitType) + internal fun paramsToUniqueNames(params: List): Map { paramNamesRecorded.clear() return params.associate { @@ -690,14 +704,6 @@ internal class CAdapterGenerator(val context: Context) : DeclarationDescriptorVi }) context.moduleDescriptor.getPackage(FqName.ROOT).accept(this, null) - - // TODO: add few predefined types. - listOf( - // context.builtIns.anyType, - // context.builtIns.getPrimitiveArrayKotlinType(PrimitiveType.INT) - ).forEach { - TypeUtils.getClassDescriptor(it)!!.accept(this@CAdapterGenerator, null) - } } private fun generateBindings() { @@ -735,12 +741,12 @@ internal class CAdapterGenerator(val context: Context) : DeclarationDescriptorVi element.isClass -> { output("${prefix}_KType* (*_type)(void);", indent) if (element.isSingletonObject) { - output("${translateType(element.declaration as ClassDescriptor)} (*_instance)();", indent) + output("${translateType((element.declaration as ClassDescriptor).defaultType)} (*_instance)();", indent) } } element.isEnumEntry -> { val enumClass = element.declaration.containingDeclaration as ClassDescriptor - output("${translateType(enumClass)} (*get)(); /* enum entry for ${element.name}. */", indent) + output("${translateType(enumClass.defaultType)} (*get)(); /* enum entry for ${element.name}. */", indent) } // TODO: handle properties. } @@ -796,11 +802,19 @@ internal class CAdapterGenerator(val context: Context) : DeclarationDescriptorVi private fun defineUsedTypes(scope: ExportedElementScope, indent: Int) { val set = mutableSetOf() defineUsedTypesImpl(scope, set) + // Add nullable primitives. + predefinedTypes.forEach { + val nullableIt = it.makeNullable() + output("typedef struct {", indent) + output("${prefix}_KNativePtr pinned;", indent + 1) + output("} ${translateType(nullableIt)};", indent) + } set.forEach { - if (isMappedToReference(it) && !it.isInlined()) { + val type = it.defaultType + if (isMappedToReference(type) && !it.isInlined()) { output("typedef struct {", indent) output("${prefix}_KNativePtr pinned;", indent + 1) - output("} ${translateType(it)};", indent) + output("} ${translateType(type)};", indent) } } } @@ -855,6 +869,11 @@ internal class CAdapterGenerator(val context: Context) : DeclarationDescriptorVi output("void (*DisposeStablePointer)(${prefix}_KNativePtr ptr);", 1) output("void (*DisposeString)(const char* string);", 1) output("${prefix}_KBoolean (*IsInstance)(${prefix}_KNativePtr ref, const ${prefix}_KType* type);", 1) + predefinedTypes.forEach { + val nullableIt = it.makeNullable() + val argument = if (!it.isUnit()) translateType(it) else "void" + output("${translateType(nullableIt)} (*${it.createNullableNameForPredefinedType})($argument);", 1) + } output("") output("/* User functions. */", 1) @@ -959,11 +978,30 @@ internal class CAdapterGenerator(val context: Context) : DeclarationDescriptorVi | return IsInstance(DerefStablePointer(ref, holder.slot()), (const KTypeInfo*)type); |} """.trimMargin()) + predefinedTypes.forEach { + assert(!it.isNothing()) + val nullableIt = it.makeNullable() + val needArgument = !it.isUnit() + val (parameter, maybeComma) = if (needArgument) + ("${translateType(it)} value" to ",") else ("" to "") + val argument = if (needArgument) "value, " else "" + output("extern \"C\" KObjHeader* Kotlin_box${it.shortNameForPredefinedType}($parameter$maybeComma KObjHeader**);") + output("static ${translateType(nullableIt)} ${it.createNullableNameForPredefinedType}Impl($parameter) {") + output("KObjHolder result_holder;", 1) + output("Kotlin_initRuntimeIfNeeded();", 1) + output("KObjHeader* result = Kotlin_box${it.shortNameForPredefinedType}($argument result_holder.slot());", 1) + output("return ${translateType(nullableIt)} { .pinned = CreateStablePointer(result) };", 1) + output("}") + } makeScopeDefinitions(top, DefinitionKind.C_SOURCE_DECLARATION, 0) output("static ${prefix}_ExportedSymbols __konan_symbols = {") output(".DisposeStablePointer = DisposeStablePointerImpl,", 1) output(".DisposeString = DisposeStringImpl,", 1) output(".IsInstance = IsInstanceImpl,", 1) + predefinedTypes.forEach { + output(".${it.createNullableNameForPredefinedType} = ${it.createNullableNameForPredefinedType}Impl,", 1) + } + makeScopeDefinitions(top, DefinitionKind.C_SOURCE_STRUCT, 1) output("};") output("RUNTIME_USED ${prefix}_ExportedSymbols* $exportedSymbol(void) { return &__konan_symbols;}") @@ -1007,8 +1045,8 @@ internal class CAdapterGenerator(val context: Context) : DeclarationDescriptorVi } } - internal fun isMappedToString(descriptor: ClassDescriptor): Boolean = - isMappedToString(descriptor.defaultType.computeBinaryType()) + internal fun isMappedToString(type: KotlinType): Boolean = + isMappedToString(type.computeBinaryType()) private fun isMappedToString(binaryType: BinaryType): Boolean = when (binaryType) { @@ -1016,12 +1054,12 @@ internal class CAdapterGenerator(val context: Context) : DeclarationDescriptorVi is BinaryType.Reference -> binaryType.types.first() == context.builtIns.string } - internal fun isMappedToReference(descriptor: ClassDescriptor) = - !isMappedToVoid(descriptor) && !isMappedToString(descriptor) && - descriptor.defaultType.binaryTypeIsReference() + internal fun isMappedToReference(type: KotlinType) = + !isMappedToVoid(type) && !isMappedToString(type) && + type.binaryTypeIsReference() - internal fun isMappedToVoid(descriptor: ClassDescriptor): Boolean { - return descriptor.isUnit() || descriptor.isNothing() + internal fun isMappedToVoid(type: KotlinType): Boolean { + return type.isUnit() || type.isNothing() } fun translateName(name: String): String { @@ -1032,11 +1070,12 @@ internal class CAdapterGenerator(val context: Context) : DeclarationDescriptorVi } } - private fun translateTypeFull(clazz: ClassDescriptor): Pair = if (isMappedToVoid(clazz)) { - "void" to "void" - } else { - translateNonVoidTypeFull(clazz.defaultType) - } + private fun translateTypeFull(type: KotlinType): Pair = + if (isMappedToVoid(type)) { + "void" to "void" + } else { + translateNonVoidTypeFull(type) + } private fun translateNonVoidTypeFull(type: KotlinType): Pair = type.unwrapToPrimitiveOrReference( eachInlinedClass = { inlinedClass, _ -> @@ -1057,9 +1096,13 @@ internal class CAdapterGenerator(val context: Context) : DeclarationDescriptorVi } ) - fun translateType(clazz: ClassDescriptor): String = translateTypeFull(clazz).first + fun translateType(element: SignatureElement): String = + translateTypeFull(element.type).first - fun translateTypeBridge(clazz: ClassDescriptor): String = translateTypeFull(clazz).second + fun translateType(type: KotlinType): String + = translateTypeFull(type).first + + fun translateTypeBridge(type: KotlinType): String = translateTypeFull(type).second fun translateTypeFqName(name: String): String { return name.replace('.', '_') diff --git a/backend.native/tests/produce_dynamic/simple/hello.kt b/backend.native/tests/produce_dynamic/simple/hello.kt index 9fd8d5e80f8..de4760bf7fb 100644 --- a/backend.native/tests/produce_dynamic/simple/hello.kt +++ b/backend.native/tests/produce_dynamic/simple/hello.kt @@ -35,8 +35,10 @@ open class Base { fun topLevelFunction(x1: Int, x2: Int) = x1 - x2 @CName("topLevelFunctionVoidFromC") -fun topLevelFunctionVoid(x1: Int, pointer: COpaquePointer?) { +fun topLevelFunctionVoid(x1: Int, x2: Int?, x3: Unit?, pointer: COpaquePointer?) { assert(x1 == 42) + assert(x2 == 77) + assert(x3 != null) assert(pointer == null) } @@ -99,6 +101,12 @@ fun useInlineClasses(ic1: IC1, ic2: IC2, ic3: IC3) { assert(ic2.value == "bar") assert(ic3.value is Base) } + +fun testNullableWithNulls(arg1: Int?, arg2: Unit?) { + assert(arg1 == null) + assert(arg2 == null) +} + fun setCErrorHandler(callback: CPointer) -> Unit>>?) { setUnhandledExceptionHook({ throwable: Throwable -> diff --git a/backend.native/tests/produce_dynamic/simple/main.c b/backend.native/tests/produce_dynamic/simple/main.c index 36816932d92..d3c132b3c24 100644 --- a/backend.native/tests/produce_dynamic/simple/main.c +++ b/backend.native/tests/produce_dynamic/simple/main.c @@ -21,6 +21,10 @@ int main(void) { T_(Enum) enum1 = __ kotlin.root.Enum.HUNDRED.get(); T_(Codeable) object1 = __ kotlin.root.get_an_object(); T_(Data) data = __ kotlin.root.getMutable(); + T_(kotlin_Int) nullableInt = __ createNullableInt(77); + T_(kotlin_Unit) nullableUnit = __ createNullableUnit(); + T_(kotlin_Int) nullableIntNull = { .pinned = 0 }; + T_(kotlin_Unit) nullableUnitNull = { .pinned = 0 }; const char* string1 = __ kotlin.root.getString(); const char* string2 = __ kotlin.root.Singleton.toString(singleton); @@ -51,12 +55,14 @@ int main(void) { printf("mutable = %s\n", string3); - topLevelFunctionVoidFromC(42, 0); - __ kotlin.root.topLevelFunctionVoid(42, 0); + topLevelFunctionVoidFromC(42, nullableInt, nullableUnit, 0); + __ kotlin.root.topLevelFunctionVoid(42, nullableInt, nullableUnit, 0); printf("topLevel = %d %d\n", topLevelFunctionFromC(780, 3), __ kotlin.root.topLevelFunctionFromCShort(5, 2)); __ kotlin.root.useInlineClasses(42, "bar", base); + __ kotlin.root.testNullableWithNulls(nullableIntNull, nullableUnitNull); + __ DisposeStablePointer(singleton.pinned); __ DisposeString(string1); __ DisposeString(string2); @@ -69,6 +75,7 @@ int main(void) { __ DisposeStablePointer(impl2.pinned); __ DisposeStablePointer(enum1.pinned); __ DisposeStablePointer(object1.pinned); + __ DisposeStablePointer(nullableInt.pinned); __ kotlin.root.setCErrorHandler(&errorHandler); __ kotlin.root.throwException(); diff --git a/runtime/src/main/kotlin/kotlin/native/internal/Boxing.kt b/runtime/src/main/kotlin/kotlin/native/internal/Boxing.kt index 33673aa1e2f..b9adfca5c55 100644 --- a/runtime/src/main/kotlin/kotlin/native/internal/Boxing.kt +++ b/runtime/src/main/kotlin/kotlin/native/internal/Boxing.kt @@ -71,3 +71,6 @@ fun boxFloat(value: Float): Float? = value @ExportForCppRuntime("Kotlin_boxDouble") fun boxDouble(value: Double): Double? = value + +@ExportForCppRuntime("Kotlin_boxUnit") +internal fun Kotlin_boxUnit(): Unit? = Unit \ No newline at end of file diff --git a/runtime/src/main/kotlin/kotlin/native/internal/RuntimeUtils.kt b/runtime/src/main/kotlin/kotlin/native/internal/RuntimeUtils.kt index 7784036c9ce..72c804a1fce 100644 --- a/runtime/src/main/kotlin/kotlin/native/internal/RuntimeUtils.kt +++ b/runtime/src/main/kotlin/kotlin/native/internal/RuntimeUtils.kt @@ -180,4 +180,4 @@ internal fun listOfInternal(vararg elements: T): List { for (i in 0 until elements.size) result.add(elements[i]) return result -} +} \ No newline at end of file