Skip to content

编译器优化 Pass 核心总结 (LLVM/IPO)

本文档总结了七种常见的编译器过程间优化(IPO)策略,涵盖其原理、处理机制及最终产生的代码效果。


1. 函数内联类 (Inlining Family)

Inline (标准内联)

  • 定义:将被调用函数(Callee)的代码直接嵌入到调用者(Caller)中,替换 call 指令。
  • 处理机制:基于代价模型 (Cost Model)。编译器计算收益(消除调用开销、暴露后续优化)与成本(代码膨胀导致 I-Cache 未命中)。只有收益 > 成本时才执行。
  • 结果
    • 消除了 call/ret 指令及参数压栈开销。
    • 上下文暴露,解锁常量传播和死代码消除。

Always-inline (强制内联)

  • 定义:忽略代价模型,强制将函数内联。
  • 处理机制:由开发者通过 __attribute__((always_inline))__forceinline 标记触发。编译器只要不遇到无限递归等硬性障碍,就会无条件内联。
  • 结果
    • 确定性:保证关键路径(如底层驱动、汇编封装)无函数调用开销。
    • 风险:滥用会导致二进制体积剧烈膨胀。

Partial-inliner (部分内联)

  • 定义:只内联函数中经常执行的“热路径”,不内联很少执行的“冷路径”(如异常处理)。
  • 处理机制
    1. 分析 CFG(控制流图)和分支概率。
    2. Outlining:将冷路径剥离成一个新的私有辅助函数。
    3. Inlining:将剩下的热路径内联到调用处。
  • 结果
    • 既享受了内联的速度优势,又避免了将庞大的错误处理代码复制多份。
    • 提高指令缓存(I-Cache)利用率。

2. 函数合并与参数优化

Mergefunc (函数合并)

  • 定义:识别并合并机器码实现完全相同的不同函数(常见于 C++ 模板)。
  • 处理机制:计算函数哈希或比较结构。若函数 A 和 B 逻辑一致,保留 A,将 B 实现为跳转到 A 或作为 A 的别名。
  • 结果
    • 显著减小二进制文件体积(ICF - Identical Code Folding)。
    • 减少指令内存占用。

Argpromotion (参数提升 / Argument Promotion)

  • 定义:将“引用传递(指针)”优化为“值传递”。
  • 处理机制:如果函数接收一个指针,但在内部:
    1. 指针未逃逸(未被转义或传给外部)。
    2. 只读取指针内容(Load),未修改(Store)。 则修改函数签名,直接传递被 Load 出来的值。
  • 结果
    • 减少内存访问(Load 指令提前到调用前或消除)。
    • 增强别名分析(Alias Analysis),因为值传递没有指针别名问题。

3. 全局与作用域优化

Internalize (内部化)

  • 定义:将本应对外可见的全局符号标记为内部私有(Internal/Static)。
  • 处理机制:在链接时优化(LTO)阶段,扫描所有模块。如果发现某全局函数或变量没有被外部库引用,将其链接属性改为 Internal
  • 结果
    • 解锁删除:使得 GlobalDCE 可以安全删除未被使用的函数。
    • 解锁修改:编译器可以随意修改其签名(如 ArgPromotion)而不必担心破坏 ABI。

Globalopt (全局变量优化)

  • 定义:优化或消除不必要的全局变量。
  • 处理机制
    1. Global to Local:若全局变量只在 main 或特定局部链中使用,降级为局部变量(进而优化进寄存器)。
    2. Constant Folding:若全局变量只写一次,将其视为常量。
    3. Heap SRA:若全局指针只用于 main 中的 malloc,优化为栈数组。
  • 结果
    • 减少全局内存访问,提升为寄存器操作。
    • 直接消除某些变量的存储分配。

4. 优化流水线协作示例

一个典型的优化链条反应:

  1. Internalize:将函数 foo() 标记为私有。
  2. ArgPromotion:因为 foo() 私有了,编译器放心大胆地把它的 int* 参数改成 int 值传递。
  3. GlobalOpt:发现传给 foo() 的那个全局变量其实可以变成局部的。
  4. Inline:将优化后变小的 foo() 内联。
  5. Mergefunc:发现内联后的代码片段与别处重复,进行合并。
  6. GlobalDCE:清理战场,删除所有不再被引用的旧函数和变量。