LiveDebugVariables

该文件比较重要(2000 行),是 LLVM 后端中用于 调试信息维护与更新 的重要组件。它实现了 LiveDebugVariables 分析器,主要用于在 寄存器分配过程中追踪用户变量的位置,确保最终生成的调试信息是准确的。

背景

在现代编译器后端,为了支持源级调试,需要在目标机器指令中准确地描述“用户级变量(user variable)”在程序执行过程中所处的位置(寄存器或内存)。LLVM 在中间阶段使用 DBG_VALUE 指令来标记变量 “此时保存在虚拟寄存器” 的位置,但在寄存器分配(Register Allocation)之后,虚拟寄存器通常被映射到物理寄存器或被“溢出”(spill)到栈槽(stack slots)。如果不做额外处理,原有的 DBG_VALUE 信息将不再准确,导致调试器无法正确地定位变量。

核心概念
  1. DBG_VALUE 指令
    • LLVM Machine IR 中的一种伪指令,用于描述“用户变量此刻存储在哪个寄存器或栈槽中”。
    • 形如 DBG_VALUE <loc>, <var metadata>,其中 <loc> 可以是虚拟寄存器、物理寄存器或栈槽。
  2. 虚拟寄存器(Virtual Registers)
    • 在寄存器分配之前使用的抽象寄存器。
    • 编译器内各个阶段生成的中间代码都以虚拟寄存器表示数据流。
  3. 寄存器分配(Register Allocation)
    • 将虚拟寄存器映射到目标机器的物理寄存器或溢出到栈上。
    • 过程中可能多次移动变量位置:寄存器⇄栈槽、一个寄存器到另一个寄存器。
  4. 溢出(Spill)与填充(Reload)
    • 当寄存器不足时,将部分虚拟寄存器内容存到栈上(spill)。
    • 在之后需要使用时,再从栈上读回(reload)。
  5. DIExpression 与调试元数据
    • DILocation、DIVariable 等 LLVM IR 元数据,用于描述源代码级的变量、作用域、位置映射等。
LiveDebugVariables 分析流程

LiveDebugVariables.cpp 则实现了 LiveDebugVariables 类,用于在寄存器分配过程中 动态追踪并更新 用户变量的位置,并在分配结束后重新生成有效的 DBG_VALUE 指令。其主要步骤如下:

  1. 删除旧的 DBG_VALUE 指令
    • 将所有与虚拟寄存器关联的 DBG_VALUE 全部移除,避免遗留过时信息。
  2. 构建“活跃变量映射”数据结构
    • 内部维护一个映射(通常以变量标识符或 DIVariable 为键),记录当前每个变量所在的位置。
  3. 在寄存器分配过程中更新映射
    • 当分配器 spill、reload 或者把值从一个寄存器移动到另一个寄存器时,实时更新该数据结构,确保对变量位置的跟踪始终准确。
  4. 寄存器分配后重新插入 DBG_VALUE
    • 遍历机器指令,针对每一个变量活跃区段(live range)在合适的位置插入新的 DBG_VALUE,描述该段时间内变量在物理寄存器或栈槽的位置。
作用与价值
  • 准确性:保证调试信息与生成的机器指令一致,避免“断点时查不到变量”或“变量值错误”。
  • 健壮性:即使经过多次寄存器重命名、spill/reload,也能正确反映变量最新位置。
  • 通用性:适用于各种目标架构和寄存器分配策略,只要在分配过程中触发位置变更事件,即可跟踪。
与其他组件的关系⚠️
  • LexicalScopes:负责将机器指令映射到源代码的词法作用域,LiveDebugVariables 则关注“变量在不同作用域的活跃位置”。
  • DwarfWriter/AsmPrinter:最终将新的 DBG_VALUE 翻译成 DWARF 调试指令,嵌入到目标文件或可执行文件中。