[K/N] More clear separation of CAdapterGenerator

Separated CAdapterBindingsBuilder into
CAdapterCodegen and CAdapterApiExporter. It makes C export much more
comprehensible and allows to split it into more clear phases.
This commit is contained in:
Sergey Bogolepov
2022-12-08 17:43:46 +02:00
committed by Space Team
parent c33013e159
commit 287337007f
7 changed files with 157 additions and 146 deletions
@@ -108,8 +108,7 @@ internal val buildCExportsPhase = konanUnitPhase(
op = {
val prefix = config.fullExportedNamePrefix.replace("-|\\.".toRegex(), "_")
val cAdapterTypeTranslator = CAdapterTypeTranslator(prefix, this.builtIns)
this.cAdapterExportedElements = CAdapterGenerator(this, this.moduleDescriptor, cAdapterTypeTranslator)
.buildExports(this.symbolTable!!)
this.cAdapterExportedElements = CAdapterGenerator(this, cAdapterTypeTranslator).buildExports(this.moduleDescriptor)
},
name = "BuildCExports",
description = "Build C exports",
@@ -5,44 +5,31 @@
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.backend.konan.NativeGenerationState
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
/**
* Third phase of C export:
* 1. Create a C++ file with runtime bindings
* 2. Create a header file with API
* 3. (MinGW only) create EXPORTS def file.
*/
internal class CAdapterApiExporter(
private val generationState: NativeGenerationState,
private val elements: CAdapterExportedElements,
) {
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 val prefix = elements.typeTranslator.prefix
private lateinit var outputStreamWriter: PrintWriter
// Primitive built-ins and unsigned types
@@ -57,92 +44,6 @@ internal class CAdapterBindingsBuilder(
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)
@@ -249,9 +150,11 @@ internal class CAdapterBindingsBuilder(
}
}
val exportedSymbols = mutableListOf<String>()
private val exportedSymbols = mutableListOf<String>()
private fun makeGlobalStruct(top: ExportedElementScope) {
// TODO: Pass temp and output files explicitly and untie from `NativeGenerationState`.
fun makeGlobalStruct() {
val top = elements.scopes.first()
val headerFile = generationState.outputFiles.cAdapterHeader
outputStreamWriter = headerFile.printWriter()
@@ -285,7 +188,7 @@ internal class CAdapterBindingsBuilder(
output("typedef double ${prefix}_KDouble;")
val typedef_KVector128 = "typedef float __attribute__ ((__vector_size__ (16))) ${prefix}_KVector128;"
if (context.config.target.family == Family.MINGW) {
if (generationState.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)
@@ -476,7 +379,7 @@ internal class CAdapterBindingsBuilder(
output("RUNTIME_USED ${prefix}_ExportedSymbols* $exportedSymbol(void) { return &__konan_symbols;}")
outputStreamWriter.close()
if (context.config.target.family == Family.MINGW) {
if (generationState.config.target.family == Family.MINGW) {
outputStreamWriter = generationState.outputFiles
.cAdapterDef
.printWriter()
@@ -0,0 +1,119 @@
/*
* 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.konan.NativeGenerationState
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.descriptors.FunctionDescriptor
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.resolve.DescriptorUtils
/**
* Second phase of C Export: build bitcode bridges from C wrappers to Kotlin functions.
*/
internal class CAdapterCodegen(
private val codegen: CodeGenerator,
override val generationState: NativeGenerationState,
) : ContextUtils {
fun buildAllAdaptersRecursively(elements: CAdapterExportedElements) {
val top = elements.scopes.single()
assert(top.kind == ScopeKind.TOP)
top.generateCAdapters(this::buildCAdapter)
}
private fun ExportedElementScope.generateCAdapters(builder: (ExportedElement) -> Unit) {
this.elements.forEach { builder(it) }
this.scopes.forEach { it.generateCAdapters(builder) }
}
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)!!
}
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.konan.exec.*
import org.jetbrains.kotlin.konan.target.*
import org.jetbrains.kotlin.konan.file.*
// Fourth phase of C export: compile runtime bindings to bitcode.
fun produceCAdapterBitcode(clang: ClangArgs, cppFileName: String, bitcodeFileName: String) {
val clangCommand = clang.clangCXX("-std=c++17", cppFileName, "-emit-llvm", "-c", "-o", bitcodeFileName)
Command(clangCommand).execute()
@@ -5,26 +5,24 @@
package org.jetbrains.kotlin.backend.konan.cexport
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.driver.phases.PsiToIrContext
import org.jetbrains.kotlin.backend.konan.llvm.*
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.ir.util.SymbolTable
import org.jetbrains.kotlin.ir.util.referenceFunction
import org.jetbrains.kotlin.konan.target.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.isChildOf
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.annotations.*
import org.jetbrains.kotlin.resolve.descriptorUtil.*
import org.jetbrains.kotlin.resolve.annotations.argumentValue
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyPublicApi
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.*
import org.jetbrains.kotlin.types.typeUtil.isUnit
internal enum class ScopeKind {
TOP,
@@ -96,11 +94,6 @@ internal class ExportedElementScope(val kind: ScopeKind, val name: String) {
return "$kind: $name ${elements.joinToString(", ")} ${scopes.joinToString("\n")}"
}
fun generateCAdapters(builder: (ExportedElement) -> Unit) {
elements.forEach { builder(it) }
scopes.forEach { it.generateCAdapters(builder) }
}
// collects names of inner scopes to make sure function<->scope name clashes would be detected, and functions would be mangled with "_" suffix
fun collectInnerScopeName(innerScope: ExportedElementScope) {
scopeNames += innerScope.name
@@ -396,17 +389,18 @@ private fun ModuleDescriptor.getPackageFragments(): List<PackageFragmentDescript
getPackage(it).fragments.filter { it.module == this }
}
/**
* First phase of C export: walk given declaration descriptors and create [CAdapterExportedElements] from them.
*/
internal class CAdapterGenerator(
private val context: PsiToIrContext,
private val moduleDescriptor: ModuleDescriptor,
private val typeTranslator: CAdapterTypeTranslator,
) : DeclarationDescriptorVisitor<Boolean, Void?> {
private val scopes = mutableListOf<ExportedElementScope>()
internal val prefix = typeTranslator.prefix
private val paramNamesRecorded = mutableMapOf<String, Int>()
private var symbolTableOrNull: SymbolTable? = null
internal val symbolTable get() = symbolTableOrNull!!
internal val symbolTable get() = context.symbolTable!!
internal fun paramsToUniqueNames(params: List<ParameterDescriptor>): Map<ParameterDescriptor, String> {
paramNamesRecorded.clear()
@@ -536,17 +530,7 @@ internal class CAdapterGenerator(
private val moduleDescriptors = mutableSetOf<ModuleDescriptor>()
fun buildExports(symbolTable: SymbolTable): CAdapterExportedElements {
this.symbolTableOrNull = symbolTable
try {
buildExports()
return CAdapterExportedElements(typeTranslator, scopes)
} finally {
this.symbolTableOrNull = null
}
}
private fun buildExports() {
fun buildExports(moduleDescriptor: ModuleDescriptor): CAdapterExportedElements {
scopes.push(ExportedElementScope(ScopeKind.TOP, "kotlin"))
moduleDescriptors += moduleDescriptor
moduleDescriptors += moduleDescriptor.getExportedDependencies(context.config)
@@ -557,6 +541,7 @@ internal class CAdapterGenerator(
})
moduleDescriptor.getPackage(FqName.ROOT).accept(this, null)
return CAdapterExportedElements(typeTranslator, scopes)
}
private val simpleNameMapping = mapOf(
@@ -15,5 +15,5 @@ internal val BuildCExports = createSimpleNamedCompilerPhase<PsiToIrContext, Fron
) { context, input ->
val prefix = context.config.fullExportedNamePrefix.replace("-|\\.".toRegex(), "_")
val typeTranslator = CAdapterTypeTranslator(prefix, context.builtIns)
CAdapterGenerator(context, input.moduleDescriptor, typeTranslator).buildExports(context.symbolTable!!)
CAdapterGenerator(context, typeTranslator).buildExports(input.moduleDescriptor)
}
@@ -10,7 +10,8 @@ import llvm.*
import org.jetbrains.kotlin.backend.common.lower.coroutines.getOrCreateFunctionWithContinuationStub
import org.jetbrains.kotlin.backend.common.lower.inline.InlinerExpressionLocationHint
import org.jetbrains.kotlin.backend.konan.*
import org.jetbrains.kotlin.backend.konan.cexport.CAdapterBindingsBuilder
import org.jetbrains.kotlin.backend.konan.cexport.CAdapterApiExporter
import org.jetbrains.kotlin.backend.konan.cexport.CAdapterCodegen
import org.jetbrains.kotlin.backend.konan.cgen.CBridgeOrigin
import org.jetbrains.kotlin.backend.konan.descriptors.*
import org.jetbrains.kotlin.backend.konan.ir.*
@@ -327,7 +328,10 @@ internal class CodeGeneratorVisitor(val generationState: NativeGenerationState,
}
}
private fun appendCAdapters() {
CAdapterBindingsBuilder(codegen, context.cAdapterExportedElements).build()
val elements = context.cAdapterExportedElements
CAdapterCodegen(codegen, generationState).buildAllAdaptersRecursively(elements)
// TODO: It is not a part of IrToBitcode. Maybe move it somewhere?
CAdapterApiExporter(generationState, elements).makeGlobalStruct()
}
private fun FunctionGenerationContext.initThreadLocalField(irField: IrField) {