LLVM 后端之旅

主要观点:现代编译器有前端、中端和后端三个主要组件,本文以 LLVM 为参考编译器,阐述编译器后端(指令选择、指令调度和寄存器分配)的操作,通过简单 C 程序示例展示后端各阶段的降低过程,包括中间端优化后的 LLVM IR 到机器 IR(MIR)的转换,以及后续的指令选择、各种优化 pass(如 early-machinelicm、livevars、phi-node-elimination、liveintervals、register-coalescer 等)、指令调度和寄存器分配等步骤,最终生成可用于 RISC-V 架构的汇编代码。

关键信息:

  • 编译器组件:前端解析源语言并转换为中端中间表示,中端对中间表示进行目标无关优化,后端将最终优化的中间端 IR 转换为目标特定的汇编。
  • 示例程序:一个简单的矩阵乘法函数matmul,展示了 C 代码到后端各阶段的转换。
  • 后端阶段:

    • 指令选择:将中间端 LLVM IR 翻译为机器 IR,选择目标特定指令表示中间端语义,每个虚拟寄存器有 gpr 或 fpr 后缀,还存在物理寄存器如$x0、$x10 等。
    • 后续 pass:包括 early-machinelicm、livevars(分析活跃变量并标记寄存器)、phi-node-elimination(删除 PHI 节点)、liveintervals(撤销 livevars 的一些标记)、register-coalescer(消除 COPY 指令)等。
    • 指令调度:重新排序指令以减少 hazards 和 stalls,不同 CPU 有特定的调度描述符,本文未指定特定 CPU 采用 RISC-V 通用信息。
    • 寄存器分配:将虚拟寄存器分配给物理寄存器,减少寄存器溢出,通过 virtregrewriter 完成实际替换。
    • 最终步骤:重新排序基本块,替换伪指令为真实指令,生成最终的 MIR,再由 AsmPrinter 生成汇编代码,可对照 RISC-V ISA 手册检查。

重要细节:

  • 为避免因 LLVM 向量化 pass 使阐述过于复杂,示例中使用 -march=rv64gc 避免 RISC-V 的向量扩展。
  • 中间端 IR 为单静态赋值(SSA)形式,变量定义一次,通过 phi 节点在循环中选择不同值。
  • 各 pass 对 MIR 进行不同的优化和调整,如删除不必要的指令、调整寄存器使用等。
  • 指令调度的最优方案依赖微架构细节,不同 CPU 有不同的调度描述符。
  • 寄存器分配要在有限的物理寄存器上复用,减少寄存器溢出。
  • AsmPrinter 主要是打印寄存器和指令,丢弃辅助信息。
阅读 17
0 条评论