org.jetbrains.kotlin.backend.konan.llvm.DebugUtils.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kotlin-native-compiler-embeddable Show documentation
Show all versions of kotlin-native-compiler-embeddable Show documentation
Embeddable JAR of Kotlin/Native compiler
/*
* 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.llvm
import kotlinx.cinterop.allocArrayOf
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.reinterpret
import llvm.*
import org.jetbrains.kotlin.backend.konan.*
import org.jetbrains.kotlin.ir.IrFileEntry
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.classOrNull
import org.jetbrains.kotlin.ir.util.SYNTHETIC_OFFSET
import org.jetbrains.kotlin.ir.util.isTypeParameter
import org.jetbrains.kotlin.ir.util.isUnsigned
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.konan.file.File
internal object DWARF {
val producer = "kotlin-compiler: ${KotlinVersion.CURRENT}"
const val debugInfoVersion = 3 /* TODO: configurable? */
/**
* This is the value taken from [DIFlags.FlagFwdDecl], to mark type declaration as
* forward one.
*/
const val flagsForwardDeclaration = 4
fun runtimeVersion(config: KonanConfig) = when (config.debugInfoVersion()) {
2 -> 0
1 -> 2 /* legacy :/ */
else -> TODO("unsupported debug info format version")
}
/**
* Note: Kotlin language constant appears in DWARF v6, while modern linker fails to links DWARF other then [2;4],
* that why we emit version 4 actually.
*/
fun dwarfVersion(config: KonanConfig) = when (config.debugInfoVersion()) {
1 -> 2
2 -> 4 /* likely the most of the future kotlin native debug info format versions will emit DWARF v4 */
else -> TODO("unsupported debug info format version")
}
fun language(config: KonanConfig) = when (config.debugInfoVersion()) {
1 -> DwarfLanguage.DW_LANG_C89.value
else -> DwarfLanguage.DW_LANG_Kotlin.value
}
}
fun KonanConfig.debugInfoVersion(): Int = configuration[KonanConfigKeys.DEBUG_INFO_VERSION] ?: 1
internal class DebugInfo(override val generationState: NativeGenerationState) : ContextUtils {
private val config = context.config
val builder: DIBuilderRef = LLVMCreateDIBuilder(llvm.module)!!
val compilationUnit: DIScopeOpaqueRef
val module: DIModuleRef
val objHeaderPointerType: DITypeOpaqueRef
init {
val path = generationState.outputFile.toFileAndFolder(config)
compilationUnit = DICreateCompilationUnit(
builder = builder,
lang = DWARF.language(config),
// we don't split path to filename and directory to provide enough level uniquely for dsymutil to avoid symbol
// clashing, which happens on linking with libraries produced from intercepting sources.
File = path.path(),
dir = "",
producer = DWARF.producer,
isOptimized = 0,
flags = "",
rv = DWARF.runtimeVersion(config)
)!!.reinterpret()
module = DICreateModule(
builder = builder,
scope = null,
name = path.path(),
configurationMacro = "",
includePath = "",
iSysRoot = "")!!
/* TODO: figure out what here 2 means:
*
* 0:b-backend-dwarf:minamoto@minamoto-osx(0)# cat /dev/null | clang -xc -S -emit-llvm -g -o - -
* ; ModuleID = '-'
* source_filename = "-"
* target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
* target triple = "x86_64-apple-macosx10.12.0"
*
* !llvm.dbg.cu = !{!0}
* !llvm.module.flags = !{!3, !4, !5}
* !llvm.ident = !{!6}
*
* !0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "Apple LLVM version 8.0.0 (clang-800.0.38)", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !2)
* !1 = !DIFile(filename: "-", directory: "/Users/minamoto/ws/.git-trees/backend-dwarf")
* !2 = !{}
* !3 = !{i32 2, !"Dwarf Version", i32 2} ; <-
* !4 = !{i32 2, !"Debug Info Version", i32 700000003} ; <-
* !5 = !{i32 1, !"PIC Level", i32 2}
* !6 = !{!"Apple LLVM version 8.0.0 (clang-800.0.38)"}
*/
val llvmTwo = llvm.int32(2)
/* TODO: from LLVM sources is unclear what runtimeVersion corresponds to term in terms of dwarf specification. */
val dwarfVersionMetaDataNodeName = "Dwarf Version".mdString(llvm.llvmContext)
val dwarfDebugInfoMetaDataNodeName = "Debug Info Version".mdString(llvm.llvmContext)
val dwarfVersion = node(llvm.llvmContext, llvmTwo, dwarfVersionMetaDataNodeName, llvm.int32(DWARF.dwarfVersion(config)))
val nodeDebugInfoVersion = node(llvm.llvmContext, llvmTwo, dwarfDebugInfoMetaDataNodeName, llvm.int32(DWARF.debugInfoVersion))
val llvmModuleFlags = "llvm.module.flags"
LLVMAddNamedMetadataOperand(llvm.module, llvmModuleFlags, dwarfVersion)
LLVMAddNamedMetadataOperand(llvm.module, llvmModuleFlags, nodeDebugInfoVersion)
val objHeaderType: DITypeOpaqueRef = DICreateStructType(
refBuilder = builder,
// TODO: here should be DIFile as scope.
scope = null,
name = "ObjHeader",
file = null,
lineNumber = 0,
sizeInBits = 0,
alignInBits = 0,
flags = DWARF.flagsForwardDeclaration,
derivedFrom = null,
elements = null,
elementsCount = 0,
refPlace = null
)!!.reinterpret()
objHeaderPointerType = dwarfPointerType(objHeaderType)
}
val files = mutableMapOf()
val subprograms = mutableMapOf()
/* Some functions are inlined on all callsites and body is eliminated by DCE, so there's no LLVM value */
val inlinedSubprograms = mutableMapOf()
val types = mutableMapOf()
private val llvmTypes = mapOf(
context.irBuiltIns.booleanType to llvm.int8Type,
context.irBuiltIns.byteType to llvm.int8Type,
context.irBuiltIns.charType to llvm.int16Type,
context.irBuiltIns.shortType to llvm.int16Type,
context.irBuiltIns.intType to llvm.int32Type,
context.irBuiltIns.longType to llvm.int64Type,
context.irBuiltIns.floatType to llvm.floatType,
context.irBuiltIns.doubleType to llvm.doubleType)
private val llvmTypeSizes = llvmTypes.map { it.key to LLVMSizeOfTypeInBits(llvmTargetData, it.value) }.toMap()
private val llvmTypeAlignments = llvmTypes.map { it.key to LLVMPreferredAlignmentOfType(llvmTargetData, it.value) }.toMap()
private val otherLlvmType = LLVMPointerType(llvm.int64Type, 0)!!
private val otherTypeSize = LLVMSizeOfTypeInBits(llvmTargetData, otherLlvmType)
private val otherTypeAlignment = LLVMPreferredAlignmentOfType(llvmTargetData, otherLlvmType)
val compilerGeneratedFile by lazy { DICreateFile(builder, "", "")!! }
val IrType.size: Long
get() = llvmTypeSizes.getOrDefault(this, otherTypeSize)
val IrType.alignment: Long
get() = llvmTypeAlignments.getOrDefault(this, otherTypeAlignment).toLong()
fun IrType.diType(llvmTargetData: LLVMTargetDataRef): DITypeOpaqueRef =
types.getOrPut(this) { dwarfType(llvmTargetData) }
fun IrFunction.subroutineType(llvmTargetData: LLVMTargetDataRef): DISubroutineTypeRef =
subroutineType(llvmTargetData, [email protected])
fun subroutineType(llvmTargetData: LLVMTargetDataRef, types: List): DISubroutineTypeRef = memScoped {
DICreateSubroutineType(builder, allocArrayOf(types.map { it.diType(llvmTargetData) }), types.size)!!
}
fun IrFile.diFileScope() = files.getOrPut(this.fileEntry.name) {
val path = this.fileEntry.name.toFileAndFolder(context.config)
DICreateFile(builder, path.file, path.folder)!!
}
fun IrFunction.diFunctionScope(
file: IrFile,
linkageName: String,
startLine: Int,
nodebug: Boolean,
) = diFunctionScope(file, name.asString(), linkageName, startLine, subroutineType(llvmTargetData), nodebug)
fun diFunctionScope(
file: IrFile,
name: String,
linkageName: String,
startLine: Int,
subroutineType: DISubroutineTypeRef,
nodebug: Boolean,
) = DICreateFunction(
builder = builder,
scope = compilationUnit,
name = (if (nodebug) "" else "") + name,
linkageName = linkageName,
file = file.diFileScope(),
lineNo = startLine,
type = subroutineType,
//TODO: need more investigations.
isLocal = 0,
isDefinition = 1,
scopeLine = 0
)!!
private fun dwarfPointerType(type: DITypeOpaqueRef): DITypeOpaqueRef =
DICreatePointerType(builder, type)!!.reinterpret()
private fun IrType.dwarfType(targetData: LLVMTargetDataRef) = when {
this.computePrimitiveBinaryTypeOrNull() != null ->
debugInfoBaseType(targetData, render(), llvmType(), encoding().value.toInt())
classOrNull != null || isTypeParameter() -> objHeaderPointerType
else -> TODO("$this: Does this case really exist?")
}
private fun debugInfoBaseType(targetData: LLVMTargetDataRef, typeName: String, type: LLVMTypeRef, encoding: Int): DITypeOpaqueRef =
DICreateBasicType(
builder, typeName,
LLVMSizeOfTypeInBits(targetData, type),
LLVMPreferredAlignmentOfType(targetData, type).toLong(), encoding
)!!.reinterpret()
private fun IrType.llvmType(): LLVMTypeRef = llvmTypes.getOrElse(this@llvmType) {
when (computePrimitiveBinaryTypeOrNull()) {
PrimitiveBinaryType.BOOLEAN -> llvm.int1Type
PrimitiveBinaryType.BYTE -> llvm.int8Type
PrimitiveBinaryType.SHORT -> llvm.int16Type
PrimitiveBinaryType.INT -> llvm.int32Type
PrimitiveBinaryType.LONG -> llvm.int64Type
PrimitiveBinaryType.FLOAT -> llvm.floatType
PrimitiveBinaryType.DOUBLE -> llvm.doubleType
PrimitiveBinaryType.VECTOR128 -> llvm.vector128Type
else -> otherLlvmType
}
}
private fun IrType.encoding(): DwarfTypeKind = when (computePrimitiveBinaryTypeOrNull()) {
PrimitiveBinaryType.FLOAT -> DwarfTypeKind.DW_ATE_float
PrimitiveBinaryType.DOUBLE -> DwarfTypeKind.DW_ATE_float
PrimitiveBinaryType.BOOLEAN -> DwarfTypeKind.DW_ATE_boolean
PrimitiveBinaryType.POINTER -> DwarfTypeKind.DW_ATE_address
else -> {
//TODO: not recursive.
if (this.isUnsigned()) DwarfTypeKind.DW_ATE_unsigned
else DwarfTypeKind.DW_ATE_signed
}
}
private val IrFunction.types: List
get() {
val parameters = valueParameters.map { it.type }
return listOf(returnType, *parameters.toTypedArray())
}
}
/**
* File entry starts offsets from zero while dwarf number lines/column starting from 1.
*/
private const val NO_SOURCE_FILE = "no source file"
private fun IrFileEntry.location(offset: Int, offsetToNumber: (Int) -> Int): Int {
// Part "name.isEmpty() || name == NO_SOURCE_FILE" is an awful hack, @minamoto, please fix properly.
if (offset == UNDEFINED_OFFSET) return 0
if (offset == SYNTHETIC_OFFSET || name.isEmpty() || name == NO_SOURCE_FILE) return 1
// lldb uses 1-based unsigned integers, so 0 is "no-info".
val result = offsetToNumber(offset) + 1
assert(result != 0)
return result
}
internal fun IrFileEntry.lineAndColumn(offset: Int): Pair {
val (line, column) = this.getLineAndColumnNumbers(offset)
return location(offset) { line } to location(offset) { column }
}
internal fun IrFileEntry.line(offset: Int) = location(offset, this::getLineNumber)
internal data class FileAndFolder(val file: String, val folder: String) {
companion object {
val NOFILE = FileAndFolder("-", "")
}
fun path() = if (this == NOFILE) file else "$folder/$file"
}
internal fun String?.toFileAndFolder(config: KonanConfig): FileAndFolder {
this ?: return FileAndFolder.NOFILE
val file = File(this).absoluteFile
var parent = file.parent
config.configuration.get(KonanConfigKeys.DEBUG_PREFIX_MAP)?.let { debugPrefixMap ->
for ((key, value) in debugPrefixMap) {
if (parent.startsWith(key)) {
parent = value + parent.removePrefix(key)
}
}
}
return FileAndFolder(file.name, parent)
}
internal fun alignTo(value: Long, align: Long): Long = (value + align - 1) / align * align
internal fun setupBridgeDebugInfo(generationState: NativeGenerationState, function: LlvmCallable): LocationInfo? {
if (!generationState.shouldContainLocationDebugInfo()) {
return null
}
val debugInfo = generationState.debugInfo
val file = debugInfo.compilerGeneratedFile
// TODO: can we share the scope among all bridges?
val scope: DIScopeOpaqueRef = function.createBridgeFunctionDebugInfo(
builder = debugInfo.builder,
scope = file.reinterpret(),
file = file,
lineNo = 0,
type = debugInfo.subroutineType(generationState.runtime.targetData, emptyList()), // TODO: use proper type.
isLocal = 0,
isDefinition = 1,
scopeLine = 0
).reinterpret()
return LocationInfo(scope, 1, 0)
}