From 2881aa44782c4dfc84a872b62c00b5f164337309 Mon Sep 17 00:00:00 2001 From: Sergey Bogolepov Date: Wed, 7 Dec 2022 13:53:17 +0200 Subject: [PATCH] [K/N][CExport] CAdapterGenerator.BindingsBuilder -> CAdapterBindingsBuilder --- .../konan/cexport/CAdapterBindingsBuilder.kt | 503 ++++++++++++++++++ .../konan/cexport/CAdapterGenerator.kt | 479 +---------------- 2 files changed, 504 insertions(+), 478 deletions(-) create mode 100644 kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cexport/CAdapterBindingsBuilder.kt diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cexport/CAdapterBindingsBuilder.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cexport/CAdapterBindingsBuilder.kt new file mode 100644 index 00000000000..e4ee783cefa --- /dev/null +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cexport/CAdapterBindingsBuilder.kt @@ -0,0 +1,503 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.backend.konan.cexport + +import kotlinx.cinterop.cValuesOf +import llvm.* +import org.jetbrains.kotlin.backend.common.pop +import org.jetbrains.kotlin.backend.konan.llvm.* +import org.jetbrains.kotlin.backend.konan.llvm.CodeGenerator +import org.jetbrains.kotlin.backend.konan.llvm.ContextUtils +import org.jetbrains.kotlin.backend.konan.llvm.ExceptionHandler +import org.jetbrains.kotlin.backend.konan.llvm.Lifetime +import org.jetbrains.kotlin.backend.konan.llvm.addLlvmFunctionWithDefaultAttributes +import org.jetbrains.kotlin.backend.konan.llvm.generateFunction +import org.jetbrains.kotlin.backend.konan.lower.getObjectClassInstanceFunction +import org.jetbrains.kotlin.builtins.UnsignedType +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies +import org.jetbrains.kotlin.ir.declarations.IrClass +import org.jetbrains.kotlin.ir.declarations.IrEnumEntry +import org.jetbrains.kotlin.ir.declarations.IrFunction +import org.jetbrains.kotlin.ir.util.isOverridable +import org.jetbrains.kotlin.konan.target.Family +import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.typeUtil.isNothing +import org.jetbrains.kotlin.types.typeUtil.isUnit +import org.jetbrains.kotlin.types.typeUtil.makeNullable +import java.io.PrintWriter + +internal class CAdapterBindingsBuilder( + val codegen: CodeGenerator, + val elements: CAdapterExportedElements, +) : ContextUtils { + override val generationState = codegen.generationState + + private val typeTranslator = elements.typeTranslator + private val builtIns = elements.typeTranslator.builtIns + private val scopes = elements.scopes + + internal val prefix = context.config.fullExportedNamePrefix.replace("-|\\.".toRegex(), "_") + private lateinit var outputStreamWriter: PrintWriter + + // Primitive built-ins and unsigned types + private val predefinedTypes = listOf( + builtIns.byteType, builtIns.shortType, + builtIns.intType, builtIns.longType, + builtIns.floatType, builtIns.doubleType, + builtIns.charType, builtIns.booleanType, + builtIns.unitType + ) + UnsignedType.values().map { + // Unfortunately, `context.ir` and `context.irBuiltins` are not initialized, so `context.ir.symbols.ubyte`, etc, are unreachable. + builtIns.builtInsModule.findClassAcrossModuleDependencies(it.classId)!!.defaultType + } + + fun build() { + val top = scopes.pop() + assert(scopes.isEmpty() && top.kind == ScopeKind.TOP) + + // Now, let's generate C world adapters for all functions. + top.generateCAdapters(::buildCAdapter) + + // Then generate data structure, describing generated adapters. + makeGlobalStruct(top) + } + + private fun buildCAdapter(exportedElement: ExportedElement): Unit = with(exportedElement) { + when { + isFunction -> { + val function = declaration as FunctionDescriptor + val irFunction = irSymbol.owner as IrFunction + cname = "_konan_function_${owner.nextFunctionIndex()}" + val llvmCallable = codegen.llvmFunction(irFunction) + // If function is virtual, we need to resolve receiver properly. + val bridge = generateFunction(codegen, llvmCallable.functionType, cname) { + val callee = if (!DescriptorUtils.isTopLevelDeclaration(function) && + irFunction.isOverridable + ) { + val receiver = param(0) + lookupVirtualImpl(receiver, irFunction) + } else { + // KT-45468: Alias insertion may not be handled by LLVM properly, in case callee is in the cache. + // Hence, insert not an alias but a wrapper, hoping it will be optimized out later. + llvmCallable + } + + val numParams = LLVMCountParams(llvmCallable.llvmValue) + val args = (0 until numParams).map { index -> param(index) } + callee.attributeProvider.addFunctionAttributes(this.function) + val result = call(callee, args, exceptionHandler = ExceptionHandler.Caller, verbatim = true) + ret(result) + } + LLVMSetLinkage(bridge, LLVMLinkage.LLVMExternalLinkage) + } + isClass -> { + val irClass = irSymbol.owner as IrClass + cname = "_konan_function_${owner.nextFunctionIndex()}" + // Produce type getter. + val getTypeFunction = addLlvmFunctionWithDefaultAttributes( + context, + llvm.module, + "${cname}_type", + kGetTypeFuncType + ) + val builder = LLVMCreateBuilderInContext(llvm.llvmContext)!! + val bb = LLVMAppendBasicBlockInContext(llvm.llvmContext, getTypeFunction, "")!! + LLVMPositionBuilderAtEnd(builder, bb) + LLVMBuildRet(builder, irClass.typeInfoPtr.llvm) + LLVMDisposeBuilder(builder) + // Produce instance getter if needed. + if (isSingletonObject) { + generateFunction(codegen, kGetObjectFuncType, "${cname}_instance") { + val value = call( + codegen.llvmFunction(context.getObjectClassInstanceFunction(irClass)), + emptyList(), + Lifetime.GLOBAL, + ExceptionHandler.Caller, + false, + returnSlot + ) + ret(value) + } + } + } + isEnumEntry -> { + // Produce entry getter. + cname = "_konan_function_${owner.nextFunctionIndex()}" + generateFunction(codegen, kGetObjectFuncType, cname) { + val irEnumEntry = irSymbol.owner as IrEnumEntry + val value = getEnumEntry(irEnumEntry, ExceptionHandler.Caller) + ret(value) + } + } + } + } + + private val kGetTypeFuncType = LLVMFunctionType(codegen.kTypeInfoPtr, null, 0, 0)!! + + // Abstraction leak for slot :(. + private val kGetObjectFuncType = LLVMFunctionType(codegen.kObjHeaderPtr, cValuesOf(codegen.kObjHeaderPtrPtr), 1, 0)!! + + private fun output(string: String, indent: Int = 0) { + if (indent != 0) outputStreamWriter.print(" " * indent) + outputStreamWriter.println(string) + } + + private fun makeElementDefinition(element: ExportedElement, kind: DefinitionKind, indent: Int) { + when (kind) { + DefinitionKind.C_HEADER_DECLARATION -> { + when { + element.isTopLevelFunction -> { + val (name, declaration) = element.makeTopLevelFunctionString() + exportedSymbols += name + output(declaration, 0) + } + } + } + + DefinitionKind.C_HEADER_STRUCT -> { + when { + element.isFunction -> + output(element.makeFunctionPointerString(), indent) + element.isClass -> { + output("${prefix}_KType* (*_type)(void);", indent) + if (element.isSingletonObject) { + output("${typeTranslator.translateType((element.declaration as ClassDescriptor).defaultType)} (*_instance)();", indent) + } + } + element.isEnumEntry -> { + val enumClass = element.declaration.containingDeclaration as ClassDescriptor + output("${typeTranslator.translateType(enumClass.defaultType)} (*get)(); /* enum entry for ${element.name}. */", indent) + } + // TODO: handle properties. + } + } + + DefinitionKind.C_SOURCE_DECLARATION -> { + when { + element.isFunction -> + output(element.makeFunctionDeclaration(), 0) + element.isClass -> + output(element.makeClassDeclaration(), 0) + element.isEnumEntry -> + output(element.makeEnumEntryDeclaration(), 0) + // TODO: handle properties. + } + } + + DefinitionKind.C_SOURCE_STRUCT -> { + when { + element.isFunction -> + output("/* ${element.name} = */ ${element.cnameImpl}, ", indent) + element.isClass -> { + output("/* Type for ${element.name} = */ ${element.cname}_type, ", indent) + if (element.isSingletonObject) + output("/* Instance for ${element.name} = */ ${element.cname}_instance_impl, ", indent) + } + element.isEnumEntry -> + output("/* enum entry getter ${element.name} = */ ${element.cname}_impl,", indent) + // TODO: handle properties. + } + } + } + } + + private fun ExportedElementScope.hasNonEmptySubScopes(): Boolean = elements.isNotEmpty() || scopes.any { it.hasNonEmptySubScopes() } + + private fun makeScopeDefinitions(scope: ExportedElementScope, kind: DefinitionKind, indent: Int) { + if (!scope.hasNonEmptySubScopes()) + return + if (kind == DefinitionKind.C_HEADER_STRUCT) output("struct {", indent) + if (kind == DefinitionKind.C_SOURCE_STRUCT) output(".${scope.name} = {", indent) + scope.scopes.forEach { + scope.collectInnerScopeName(it) + makeScopeDefinitions(it, kind, indent + 1) + } + scope.elements.forEach { makeElementDefinition(it, kind, indent + 1) } + if (kind == DefinitionKind.C_HEADER_STRUCT) output("} ${scope.name};", indent) + if (kind == DefinitionKind.C_SOURCE_STRUCT) output("},", indent) + } + + private fun defineUsedTypesImpl(scope: ExportedElementScope, set: MutableSet) { + scope.elements.forEach { + it.addUsedTypes(set) + } + scope.scopes.forEach { + defineUsedTypesImpl(it, set) + } + } + + private fun defineUsedTypes(scope: ExportedElementScope, indent: Int) { + val usedTypes = mutableSetOf() + defineUsedTypesImpl(scope, usedTypes) + val usedReferenceTypes = usedTypes.filter { typeTranslator.isMappedToReference(it) } + // Add nullable primitives, which are used in prototypes of "(*createNullable)" + val predefinedNullableTypes: List = predefinedTypes.map { it.makeNullable() } + + (predefinedNullableTypes + usedReferenceTypes) + .map { typeTranslator.translateType(it) } + .toSet() + .forEach { + output("typedef struct {", indent) + output("${prefix}_KNativePtr pinned;", indent + 1) + output("} $it;", indent) + } + } + + val exportedSymbols = mutableListOf() + + private fun makeGlobalStruct(top: ExportedElementScope) { + val headerFile = generationState.outputFiles.cAdapterHeader + outputStreamWriter = headerFile.printWriter() + + val exportedSymbol = "${prefix}_symbols" + exportedSymbols += exportedSymbol + + output("#ifndef KONAN_${prefix.uppercase()}_H") + output("#define KONAN_${prefix.uppercase()}_H") + // TODO: use namespace for C++ case? + output(""" + #ifdef __cplusplus + extern "C" { + #endif""".trimIndent()) + output(""" + #ifdef __cplusplus + typedef bool ${prefix}_KBoolean; + #else + typedef _Bool ${prefix}_KBoolean; + #endif + """.trimIndent()) + output("typedef unsigned short ${prefix}_KChar;") + output("typedef signed char ${prefix}_KByte;") + output("typedef short ${prefix}_KShort;") + output("typedef int ${prefix}_KInt;") + output("typedef long long ${prefix}_KLong;") + output("typedef unsigned char ${prefix}_KUByte;") + output("typedef unsigned short ${prefix}_KUShort;") + output("typedef unsigned int ${prefix}_KUInt;") + output("typedef unsigned long long ${prefix}_KULong;") + output("typedef float ${prefix}_KFloat;") + output("typedef double ${prefix}_KDouble;") + + val typedef_KVector128 = "typedef float __attribute__ ((__vector_size__ (16))) ${prefix}_KVector128;" + if (context.config.target.family == Family.MINGW) { + // Separate `output` for each line to ensure Windows EOL (LFCR), otherwise generated file will have inconsistent line ending. + output("#ifndef _MSC_VER") + output(typedef_KVector128) + output("#else") + output("#include ") + output("typedef __m128 ${prefix}_KVector128;") + output("#endif") + } else { + output(typedef_KVector128) + } + + output("typedef void* ${prefix}_KNativePtr;") + output("struct ${prefix}_KType;") + output("typedef struct ${prefix}_KType ${prefix}_KType;") + + output("") + defineUsedTypes(top, 0) + + output("") + makeScopeDefinitions(top, DefinitionKind.C_HEADER_DECLARATION, 0) + + output("") + output("typedef struct {") + output("/* Service functions. */", 1) + 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()) typeTranslator.translateType(it) else "void" + output("${typeTranslator.translateType(nullableIt)} (*${it.createNullableNameForPredefinedType})($argument);", 1) + if (!it.isUnit()) + output("$argument (*${it.createGetNonNullValueOfPredefinedType})(${typeTranslator.translateType(nullableIt)});", 1) + } + + output("") + output("/* User functions. */", 1) + makeScopeDefinitions(top, DefinitionKind.C_HEADER_STRUCT, 1) + output("} ${prefix}_ExportedSymbols;") + + output("extern ${prefix}_ExportedSymbols* $exportedSymbol(void);") + output(""" + #ifdef __cplusplus + } /* extern "C" */ + #endif""".trimIndent()) + + output("#endif /* KONAN_${prefix.uppercase()}_H */") + + outputStreamWriter.close() + println("Produced library API in ${prefix}_api.h") + + outputStreamWriter = generationState.tempFiles + .cAdapterCpp + .printWriter() + + // Include header into C++ source. + headerFile.forEachLine { it -> output(it) } + + output("#include ") + + output(""" + |struct KObjHeader; + |typedef struct KObjHeader KObjHeader; + |struct KTypeInfo; + |typedef struct KTypeInfo KTypeInfo; + | + |struct FrameOverlay; + |typedef struct FrameOverlay FrameOverlay; + | + |#define RUNTIME_NOTHROW __attribute__((nothrow)) + |#define RUNTIME_USED __attribute__((used)) + |#define RUNTIME_NORETURN __attribute__((noreturn)) + | + |extern "C" { + |void UpdateStackRef(KObjHeader**, const KObjHeader*) RUNTIME_NOTHROW; + |KObjHeader* AllocInstance(const KTypeInfo*, KObjHeader**) RUNTIME_NOTHROW; + |KObjHeader* DerefStablePointer(void*, KObjHeader**) RUNTIME_NOTHROW; + |void* CreateStablePointer(KObjHeader*) RUNTIME_NOTHROW; + |void DisposeStablePointer(void*) RUNTIME_NOTHROW; + |${prefix}_KBoolean IsInstance(const KObjHeader*, const KTypeInfo*) RUNTIME_NOTHROW; + |void EnterFrame(KObjHeader** start, int parameters, int count) RUNTIME_NOTHROW; + |void LeaveFrame(KObjHeader** start, int parameters, int count) RUNTIME_NOTHROW; + |void SetCurrentFrame(KObjHeader** start) RUNTIME_NOTHROW; + |FrameOverlay* getCurrentFrame() RUNTIME_NOTHROW; + |void Kotlin_initRuntimeIfNeeded(); + |void Kotlin_mm_switchThreadStateRunnable() RUNTIME_NOTHROW; + |void Kotlin_mm_switchThreadStateNative() RUNTIME_NOTHROW; + |void HandleCurrentExceptionWhenLeavingKotlinCode(); + | + |KObjHeader* CreateStringFromCString(const char*, KObjHeader**); + |char* CreateCStringFromString(const KObjHeader*); + |void DisposeCString(char* cstring); + |} // extern "C" + | + |struct ${prefix}_FrameOverlay { + | void* arena; + | ${prefix}_FrameOverlay* previous; + | ${prefix}_KInt parameters; + | ${prefix}_KInt count; + |}; + | + |class KObjHolder { + |public: + | KObjHolder() : obj_(nullptr) { + | EnterFrame(frame(), 0, sizeof(*this)/sizeof(void*)); + | } + | explicit KObjHolder(const KObjHeader* obj) : obj_(nullptr) { + | EnterFrame(frame(), 0, sizeof(*this)/sizeof(void*)); + | UpdateStackRef(&obj_, obj); + | } + | ~KObjHolder() { + | LeaveFrame(frame(), 0, sizeof(*this)/sizeof(void*)); + | } + | KObjHeader* obj() { return obj_; } + | KObjHeader** slot() { return &obj_; } + | private: + | ${prefix}_FrameOverlay frame_; + | KObjHeader* obj_; + | + | KObjHeader** frame() { return reinterpret_cast(&frame_); } + |}; + | + |class ScopedRunnableState { + |public: + | ScopedRunnableState() noexcept { Kotlin_mm_switchThreadStateRunnable(); } + | ~ScopedRunnableState() { Kotlin_mm_switchThreadStateNative(); } + | ScopedRunnableState(const ScopedRunnableState&) = delete; + | ScopedRunnableState(ScopedRunnableState&&) = delete; + | ScopedRunnableState& operator=(const ScopedRunnableState&) = delete; + | ScopedRunnableState& operator=(ScopedRunnableState&&) = delete; + |}; + | + |static void DisposeStablePointerImpl(${prefix}_KNativePtr ptr) { + | Kotlin_initRuntimeIfNeeded(); + | ScopedRunnableState stateGuard; + | DisposeStablePointer(ptr); + |} + |static void DisposeStringImpl(const char* ptr) { + | DisposeCString((char*)ptr); + |} + |static ${prefix}_KBoolean IsInstanceImpl(${prefix}_KNativePtr ref, const ${prefix}_KType* type) { + | Kotlin_initRuntimeIfNeeded(); + | ScopedRunnableState stateGuard; + | KObjHolder holder; + | 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) + ("${typeTranslator.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 ${typeTranslator.translateType(nullableIt)} ${it.createNullableNameForPredefinedType}Impl($parameter) {") + output("Kotlin_initRuntimeIfNeeded();", 1) + output("ScopedRunnableState stateGuard;", 1) + output("KObjHolder result_holder;", 1) + output("KObjHeader* result = Kotlin_box${it.shortNameForPredefinedType}($argument result_holder.slot());", 1) + output("return ${typeTranslator.translateType(nullableIt)} { .pinned = CreateStablePointer(result) };", 1) + output("}") + + if (!it.isUnit()) { + output("extern \"C\" ${typeTranslator.translateType(it)} Kotlin_unbox${it.shortNameForPredefinedType}(KObjHeader*);") + output("static ${typeTranslator.translateType(it)} ${it.createGetNonNullValueOfPredefinedType}Impl(${typeTranslator.translateType(nullableIt)} value) {") + output("Kotlin_initRuntimeIfNeeded();", 1) + output("ScopedRunnableState stateGuard;", 1) + output("KObjHolder value_holder;", 1) + output("return Kotlin_unbox${it.shortNameForPredefinedType}(DerefStablePointer(value.pinned, value_holder.slot()));", 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) + if (!it.isUnit()) { + output(".${it.createGetNonNullValueOfPredefinedType} = ${it.createGetNonNullValueOfPredefinedType}Impl,", 1) + } + } + + makeScopeDefinitions(top, DefinitionKind.C_SOURCE_STRUCT, 1) + output("};") + output("RUNTIME_USED ${prefix}_ExportedSymbols* $exportedSymbol(void) { return &__konan_symbols;}") + outputStreamWriter.close() + + if (context.config.target.family == Family.MINGW) { + outputStreamWriter = generationState.outputFiles + .cAdapterDef + .printWriter() + output("EXPORTS") + exportedSymbols.forEach { output(it) } + outputStreamWriter.close() + } + } +} + +private val KotlinType.createNullableNameForPredefinedType + get() = "createNullable${this.shortNameForPredefinedType}" + +private val KotlinType.createGetNonNullValueOfPredefinedType + get() = "getNonNullValueOf${this.shortNameForPredefinedType}" + +private operator fun String.times(count: Int): String { + val builder = StringBuilder() + repeat(count, { builder.append(this) }) + return builder.toString() +} + +private val KotlinType.shortNameForPredefinedType + get() = this.toString().split('.').last() \ No newline at end of file diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cexport/CAdapterGenerator.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cexport/CAdapterGenerator.kt index 8657950d75a..27226bb541b 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cexport/CAdapterGenerator.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cexport/CAdapterGenerator.kt @@ -5,22 +5,15 @@ package org.jetbrains.kotlin.backend.konan.cexport -import kotlinx.cinterop.cValuesOf import llvm.* import org.jetbrains.kotlin.backend.common.descriptors.allParameters import org.jetbrains.kotlin.backend.common.pop import org.jetbrains.kotlin.backend.common.push import org.jetbrains.kotlin.backend.konan.* import org.jetbrains.kotlin.backend.konan.llvm.* -import org.jetbrains.kotlin.backend.konan.lower.getObjectClassInstanceFunction -import org.jetbrains.kotlin.builtins.UnsignedType import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor -import org.jetbrains.kotlin.ir.declarations.IrClass -import org.jetbrains.kotlin.ir.declarations.IrEnumEntry -import org.jetbrains.kotlin.ir.declarations.IrFunction import org.jetbrains.kotlin.ir.util.SymbolTable -import org.jetbrains.kotlin.ir.util.isOverridable import org.jetbrains.kotlin.ir.util.referenceFunction import org.jetbrains.kotlin.konan.target.* import org.jetbrains.kotlin.name.FqName @@ -31,7 +24,6 @@ import org.jetbrains.kotlin.resolve.annotations.* import org.jetbrains.kotlin.resolve.descriptorUtil.* import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.typeUtil.* -import java.io.PrintWriter internal enum class ScopeKind { TOP, @@ -57,22 +49,6 @@ private enum class Direction { C_TO_KOTLIN } -private operator fun String.times(count: Int): String { - val builder = StringBuilder() - repeat(count, { builder.append(this) }) - return builder.toString() -} - -private val KotlinType.shortNameForPredefinedType - get() = this.toString().split('.').last() - - -private val KotlinType.createNullableNameForPredefinedType - get() = "createNullable${this.shortNameForPredefinedType}" - -private val KotlinType.createGetNonNullValueOfPredefinedType - get() = "getNonNullValueOf${this.shortNameForPredefinedType}" - private fun isExportedFunction(descriptor: FunctionDescriptor): Boolean { if (!descriptor.isEffectivelyPublicApi || !descriptor.kind.isReal || descriptor.isExpect) return false @@ -602,458 +578,5 @@ internal class CAdapterGenerator( private var functionIndex = 0 fun nextFunctionIndex() = functionIndex++ - fun generateBindings(codegen: CodeGenerator) = BindingsBuilder(codegen, cAdapterExportedElements).build() - - class BindingsBuilder( - val codegen: CodeGenerator, - val elements: CAdapterExportedElements, - ) : ContextUtils { - override val generationState = codegen.generationState - - private val typeTranslator = elements.typeTranslator - private val builtIns = elements.typeTranslator.builtIns - private val scopes = elements.scopes - - internal val prefix = context.config.fullExportedNamePrefix.replace("-|\\.".toRegex(), "_") - private lateinit var outputStreamWriter: PrintWriter - - // Primitive built-ins and unsigned types - private val predefinedTypes = listOf( - builtIns.byteType, builtIns.shortType, - builtIns.intType, builtIns.longType, - builtIns.floatType, builtIns.doubleType, - builtIns.charType, builtIns.booleanType, - builtIns.unitType - ) + UnsignedType.values().map { - // Unfortunately, `context.ir` and `context.irBuiltins` are not initialized, so `context.ir.symbols.ubyte`, etc, are unreachable. - builtIns.builtInsModule.findClassAcrossModuleDependencies(it.classId)!!.defaultType - } - - fun build() { - val top = scopes.pop() - assert(scopes.isEmpty() && top.kind == ScopeKind.TOP) - - // Now, let's generate C world adapters for all functions. - top.generateCAdapters(::buildCAdapter) - - // Then generate data structure, describing generated adapters. - makeGlobalStruct(top) - } - - private fun buildCAdapter(exportedElement: ExportedElement): Unit = with(exportedElement) { - when { - isFunction -> { - val function = declaration as FunctionDescriptor - val irFunction = irSymbol.owner as IrFunction - cname = "_konan_function_${owner.nextFunctionIndex()}" - val llvmCallable = codegen.llvmFunction(irFunction) - // If function is virtual, we need to resolve receiver properly. - val bridge = generateFunction(codegen, llvmCallable.functionType, cname) { - val callee = if (!DescriptorUtils.isTopLevelDeclaration(function) && - irFunction.isOverridable) { - val receiver = param(0) - lookupVirtualImpl(receiver, irFunction) - } else { - // KT-45468: Alias insertion may not be handled by LLVM properly, in case callee is in the cache. - // Hence, insert not an alias but a wrapper, hoping it will be optimized out later. - llvmCallable - } - - val numParams = LLVMCountParams(llvmCallable.llvmValue) - val args = (0 until numParams).map { index -> param(index) } - callee.attributeProvider.addFunctionAttributes(this.function) - val result = call(callee, args, exceptionHandler = ExceptionHandler.Caller, verbatim = true) - ret(result) - } - LLVMSetLinkage(bridge, LLVMLinkage.LLVMExternalLinkage) - } - isClass -> { - val irClass = irSymbol.owner as IrClass - cname = "_konan_function_${owner.nextFunctionIndex()}" - // Produce type getter. - val getTypeFunction = addLlvmFunctionWithDefaultAttributes( - context, - llvm.module, - "${cname}_type", - kGetTypeFuncType - ) - val builder = LLVMCreateBuilderInContext(llvm.llvmContext)!! - val bb = LLVMAppendBasicBlockInContext(llvm.llvmContext, getTypeFunction, "")!! - LLVMPositionBuilderAtEnd(builder, bb) - LLVMBuildRet(builder, irClass.typeInfoPtr.llvm) - LLVMDisposeBuilder(builder) - // Produce instance getter if needed. - if (isSingletonObject) { - generateFunction(codegen, kGetObjectFuncType, "${cname}_instance") { - val value = call( - codegen.llvmFunction(context.getObjectClassInstanceFunction(irClass)), - emptyList(), - Lifetime.GLOBAL, - ExceptionHandler.Caller, - false, - returnSlot) - ret(value) - } - } - } - isEnumEntry -> { - // Produce entry getter. - cname = "_konan_function_${owner.nextFunctionIndex()}" - generateFunction(codegen, kGetObjectFuncType, cname) { - val irEnumEntry = irSymbol.owner as IrEnumEntry - val value = getEnumEntry(irEnumEntry, ExceptionHandler.Caller) - ret(value) - } - } - } - } - - private val kGetTypeFuncType = LLVMFunctionType(codegen.kTypeInfoPtr, null, 0, 0)!! - - // Abstraction leak for slot :(. - private val kGetObjectFuncType = LLVMFunctionType(codegen.kObjHeaderPtr, cValuesOf(codegen.kObjHeaderPtrPtr), 1, 0)!! - - private fun output(string: String, indent: Int = 0) { - if (indent != 0) outputStreamWriter.print(" " * indent) - outputStreamWriter.println(string) - } - - private fun makeElementDefinition(element: ExportedElement, kind: DefinitionKind, indent: Int) { - when (kind) { - DefinitionKind.C_HEADER_DECLARATION -> { - when { - element.isTopLevelFunction -> { - val (name, declaration) = element.makeTopLevelFunctionString() - exportedSymbols += name - output(declaration, 0) - } - } - } - - DefinitionKind.C_HEADER_STRUCT -> { - when { - element.isFunction -> - output(element.makeFunctionPointerString(), indent) - element.isClass -> { - output("${prefix}_KType* (*_type)(void);", indent) - if (element.isSingletonObject) { - output("${typeTranslator.translateType((element.declaration as ClassDescriptor).defaultType)} (*_instance)();", indent) - } - } - element.isEnumEntry -> { - val enumClass = element.declaration.containingDeclaration as ClassDescriptor - output("${typeTranslator.translateType(enumClass.defaultType)} (*get)(); /* enum entry for ${element.name}. */", indent) - } - // TODO: handle properties. - } - } - - DefinitionKind.C_SOURCE_DECLARATION -> { - when { - element.isFunction -> - output(element.makeFunctionDeclaration(), 0) - element.isClass -> - output(element.makeClassDeclaration(), 0) - element.isEnumEntry -> - output(element.makeEnumEntryDeclaration(), 0) - // TODO: handle properties. - } - } - - DefinitionKind.C_SOURCE_STRUCT -> { - when { - element.isFunction -> - output("/* ${element.name} = */ ${element.cnameImpl}, ", indent) - element.isClass -> { - output("/* Type for ${element.name} = */ ${element.cname}_type, ", indent) - if (element.isSingletonObject) - output("/* Instance for ${element.name} = */ ${element.cname}_instance_impl, ", indent) - } - element.isEnumEntry -> - output("/* enum entry getter ${element.name} = */ ${element.cname}_impl,", indent) - // TODO: handle properties. - } - } - } - } - - private fun ExportedElementScope.hasNonEmptySubScopes(): Boolean = elements.isNotEmpty() || scopes.any { it.hasNonEmptySubScopes() } - - private fun makeScopeDefinitions(scope: ExportedElementScope, kind: DefinitionKind, indent: Int) { - if (!scope.hasNonEmptySubScopes()) - return - if (kind == DefinitionKind.C_HEADER_STRUCT) output("struct {", indent) - if (kind == DefinitionKind.C_SOURCE_STRUCT) output(".${scope.name} = {", indent) - scope.scopes.forEach { - scope.collectInnerScopeName(it) - makeScopeDefinitions(it, kind, indent + 1) - } - scope.elements.forEach { makeElementDefinition(it, kind, indent + 1) } - if (kind == DefinitionKind.C_HEADER_STRUCT) output("} ${scope.name};", indent) - if (kind == DefinitionKind.C_SOURCE_STRUCT) output("},", indent) - } - - private fun defineUsedTypesImpl(scope: ExportedElementScope, set: MutableSet) { - scope.elements.forEach { - it.addUsedTypes(set) - } - scope.scopes.forEach { - defineUsedTypesImpl(it, set) - } - } - - private fun defineUsedTypes(scope: ExportedElementScope, indent: Int) { - val usedTypes = mutableSetOf() - defineUsedTypesImpl(scope, usedTypes) - val usedReferenceTypes = usedTypes.filter { typeTranslator.isMappedToReference(it) } - // Add nullable primitives, which are used in prototypes of "(*createNullable)" - val predefinedNullableTypes: List = predefinedTypes.map { it.makeNullable() } - - (predefinedNullableTypes + usedReferenceTypes) - .map { typeTranslator.translateType(it) } - .toSet() - .forEach { - output("typedef struct {", indent) - output("${prefix}_KNativePtr pinned;", indent + 1) - output("} $it;", indent) - } - } - - val exportedSymbols = mutableListOf() - - private fun makeGlobalStruct(top: ExportedElementScope) { - val headerFile = generationState.outputFiles.cAdapterHeader - outputStreamWriter = headerFile.printWriter() - - val exportedSymbol = "${prefix}_symbols" - exportedSymbols += exportedSymbol - - output("#ifndef KONAN_${prefix.uppercase()}_H") - output("#define KONAN_${prefix.uppercase()}_H") - // TODO: use namespace for C++ case? - output(""" - #ifdef __cplusplus - extern "C" { - #endif""".trimIndent()) - output(""" - #ifdef __cplusplus - typedef bool ${prefix}_KBoolean; - #else - typedef _Bool ${prefix}_KBoolean; - #endif - """.trimIndent()) - output("typedef unsigned short ${prefix}_KChar;") - output("typedef signed char ${prefix}_KByte;") - output("typedef short ${prefix}_KShort;") - output("typedef int ${prefix}_KInt;") - output("typedef long long ${prefix}_KLong;") - output("typedef unsigned char ${prefix}_KUByte;") - output("typedef unsigned short ${prefix}_KUShort;") - output("typedef unsigned int ${prefix}_KUInt;") - output("typedef unsigned long long ${prefix}_KULong;") - output("typedef float ${prefix}_KFloat;") - output("typedef double ${prefix}_KDouble;") - - val typedef_KVector128 = "typedef float __attribute__ ((__vector_size__ (16))) ${prefix}_KVector128;" - if (context.config.target.family == Family.MINGW) { - // Separate `output` for each line to ensure Windows EOL (LFCR), otherwise generated file will have inconsistent line ending. - output("#ifndef _MSC_VER") - output(typedef_KVector128) - output("#else") - output("#include ") - output("typedef __m128 ${prefix}_KVector128;") - output("#endif") - } else { - output(typedef_KVector128) - } - - output("typedef void* ${prefix}_KNativePtr;") - output("struct ${prefix}_KType;") - output("typedef struct ${prefix}_KType ${prefix}_KType;") - - output("") - defineUsedTypes(top, 0) - - output("") - makeScopeDefinitions(top, DefinitionKind.C_HEADER_DECLARATION, 0) - - output("") - output("typedef struct {") - output("/* Service functions. */", 1) - 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()) typeTranslator.translateType(it) else "void" - output("${typeTranslator.translateType(nullableIt)} (*${it.createNullableNameForPredefinedType})($argument);", 1) - if(!it.isUnit()) - output("$argument (*${it.createGetNonNullValueOfPredefinedType})(${typeTranslator.translateType(nullableIt)});", 1) - } - - output("") - output("/* User functions. */", 1) - makeScopeDefinitions(top, DefinitionKind.C_HEADER_STRUCT, 1) - output("} ${prefix}_ExportedSymbols;") - - output("extern ${prefix}_ExportedSymbols* $exportedSymbol(void);") - output(""" - #ifdef __cplusplus - } /* extern "C" */ - #endif""".trimIndent()) - - output("#endif /* KONAN_${prefix.uppercase()}_H */") - - outputStreamWriter.close() - println("Produced library API in ${prefix}_api.h") - - outputStreamWriter = generationState.tempFiles - .cAdapterCpp - .printWriter() - - // Include header into C++ source. - headerFile.forEachLine { it -> output(it) } - - output("#include ") - - output(""" - |struct KObjHeader; - |typedef struct KObjHeader KObjHeader; - |struct KTypeInfo; - |typedef struct KTypeInfo KTypeInfo; - | - |struct FrameOverlay; - |typedef struct FrameOverlay FrameOverlay; - | - |#define RUNTIME_NOTHROW __attribute__((nothrow)) - |#define RUNTIME_USED __attribute__((used)) - |#define RUNTIME_NORETURN __attribute__((noreturn)) - | - |extern "C" { - |void UpdateStackRef(KObjHeader**, const KObjHeader*) RUNTIME_NOTHROW; - |KObjHeader* AllocInstance(const KTypeInfo*, KObjHeader**) RUNTIME_NOTHROW; - |KObjHeader* DerefStablePointer(void*, KObjHeader**) RUNTIME_NOTHROW; - |void* CreateStablePointer(KObjHeader*) RUNTIME_NOTHROW; - |void DisposeStablePointer(void*) RUNTIME_NOTHROW; - |${prefix}_KBoolean IsInstance(const KObjHeader*, const KTypeInfo*) RUNTIME_NOTHROW; - |void EnterFrame(KObjHeader** start, int parameters, int count) RUNTIME_NOTHROW; - |void LeaveFrame(KObjHeader** start, int parameters, int count) RUNTIME_NOTHROW; - |void SetCurrentFrame(KObjHeader** start) RUNTIME_NOTHROW; - |FrameOverlay* getCurrentFrame() RUNTIME_NOTHROW; - |void Kotlin_initRuntimeIfNeeded(); - |void Kotlin_mm_switchThreadStateRunnable() RUNTIME_NOTHROW; - |void Kotlin_mm_switchThreadStateNative() RUNTIME_NOTHROW; - |void HandleCurrentExceptionWhenLeavingKotlinCode(); - | - |KObjHeader* CreateStringFromCString(const char*, KObjHeader**); - |char* CreateCStringFromString(const KObjHeader*); - |void DisposeCString(char* cstring); - |} // extern "C" - | - |struct ${prefix}_FrameOverlay { - | void* arena; - | ${prefix}_FrameOverlay* previous; - | ${prefix}_KInt parameters; - | ${prefix}_KInt count; - |}; - | - |class KObjHolder { - |public: - | KObjHolder() : obj_(nullptr) { - | EnterFrame(frame(), 0, sizeof(*this)/sizeof(void*)); - | } - | explicit KObjHolder(const KObjHeader* obj) : obj_(nullptr) { - | EnterFrame(frame(), 0, sizeof(*this)/sizeof(void*)); - | UpdateStackRef(&obj_, obj); - | } - | ~KObjHolder() { - | LeaveFrame(frame(), 0, sizeof(*this)/sizeof(void*)); - | } - | KObjHeader* obj() { return obj_; } - | KObjHeader** slot() { return &obj_; } - | private: - | ${prefix}_FrameOverlay frame_; - | KObjHeader* obj_; - | - | KObjHeader** frame() { return reinterpret_cast(&frame_); } - |}; - | - |class ScopedRunnableState { - |public: - | ScopedRunnableState() noexcept { Kotlin_mm_switchThreadStateRunnable(); } - | ~ScopedRunnableState() { Kotlin_mm_switchThreadStateNative(); } - | ScopedRunnableState(const ScopedRunnableState&) = delete; - | ScopedRunnableState(ScopedRunnableState&&) = delete; - | ScopedRunnableState& operator=(const ScopedRunnableState&) = delete; - | ScopedRunnableState& operator=(ScopedRunnableState&&) = delete; - |}; - | - |static void DisposeStablePointerImpl(${prefix}_KNativePtr ptr) { - | Kotlin_initRuntimeIfNeeded(); - | ScopedRunnableState stateGuard; - | DisposeStablePointer(ptr); - |} - |static void DisposeStringImpl(const char* ptr) { - | DisposeCString((char*)ptr); - |} - |static ${prefix}_KBoolean IsInstanceImpl(${prefix}_KNativePtr ref, const ${prefix}_KType* type) { - | Kotlin_initRuntimeIfNeeded(); - | ScopedRunnableState stateGuard; - | KObjHolder holder; - | 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) - ("${typeTranslator.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 ${typeTranslator.translateType(nullableIt)} ${it.createNullableNameForPredefinedType}Impl($parameter) {") - output("Kotlin_initRuntimeIfNeeded();", 1) - output("ScopedRunnableState stateGuard;", 1) - output("KObjHolder result_holder;", 1) - output("KObjHeader* result = Kotlin_box${it.shortNameForPredefinedType}($argument result_holder.slot());", 1) - output("return ${typeTranslator.translateType(nullableIt)} { .pinned = CreateStablePointer(result) };", 1) - output("}") - - if (!it.isUnit()) { - output("extern \"C\" ${typeTranslator.translateType(it)} Kotlin_unbox${it.shortNameForPredefinedType}(KObjHeader*);") - output("static ${typeTranslator.translateType(it)} ${it.createGetNonNullValueOfPredefinedType}Impl(${typeTranslator.translateType(nullableIt)} value) {") - output("Kotlin_initRuntimeIfNeeded();", 1) - output("ScopedRunnableState stateGuard;", 1) - output("KObjHolder value_holder;", 1) - output("return Kotlin_unbox${it.shortNameForPredefinedType}(DerefStablePointer(value.pinned, value_holder.slot()));", 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) - if (!it.isUnit()) { - output(".${it.createGetNonNullValueOfPredefinedType} = ${it.createGetNonNullValueOfPredefinedType}Impl,", 1) - } - } - - makeScopeDefinitions(top, DefinitionKind.C_SOURCE_STRUCT, 1) - output("};") - output("RUNTIME_USED ${prefix}_ExportedSymbols* $exportedSymbol(void) { return &__konan_symbols;}") - outputStreamWriter.close() - - if (context.config.target.family == Family.MINGW) { - outputStreamWriter = generationState.outputFiles - .cAdapterDef - .printWriter() - output("EXPORTS") - exportedSymbols.forEach { output(it) } - outputStreamWriter.close() - } - } - } + fun generateBindings(codegen: CodeGenerator) = CAdapterBindingsBuilder(codegen, cAdapterExportedElements).build() }