12.4 数据流分析框架
许多优化需要超出一条指令的全局事实:哪些定义可能到达当前块?哪些表达式在所有来路上都已经算过?哪些值未来还可能被使用?数据流分析(data-flow analysis)会在控制流图上计算这些事实,直到每个位置都彼此一致。
编译器不会为每种分析从零实现求解器,而是复用一个包含四项选择的框架:
- 事实的域(domain),通常是集合或格元素;
- 分析的方向(direction),向前或向后;
- 合并前驱/后继事实的 meet 运算;
- 描述每个块如何改变事实的传递函数(transfer function)。
边界条件说明函数 entry 或 exit 已知什么;其他块如何初始化,则取决于分析要求最小不动点还是最大不动点。
向前与向后方程
向前分析使用:
IN[B] = 对所有前驱 P 的 OUT[P] 做 meet
OUT[B] = transfer_B(IN[B])向后分析使用:
OUT[B] = 对所有后继 S 的 IN[S] 做 meet
IN[B] = transfer_B(OUT[B])到达定义(reaching definitions)是向前 may-analysis,meet 使用并集,因为只要定义能沿任意前驱路径到达,就应进入结果:
OUT[B] = GEN[B] ∪ (IN[B] − KILL[B])可用表达式(available expressions)是向前 must-analysis,meet 使用交集,因为表达式必须在每一条来路上都已计算且未被 kill,才能称为可用。
一个到达定义的汇合例子
假设分支前定义 x1,左分支重新定义 x2,右分支保持旧值。汇合点的到达集合是 {x1, x2}:某些执行带着旧定义到达,另一些带着新定义到达。如果汇合块再定义 x3,其 transfer 会 kill 两个旧定义并生成 {x3}。
分析描述的是可能性,不是某次运行历史。它并非声称 x1、x2 在同一次执行中都发生,而是说二者任一都可能到达汇合。消费该结果的优化必须理解这一点。
迭代到不动点
循环会产生环形方程:header 依赖 latch,latch 又依赖 header。工作列表求解器从规定的初始值出发应用 transfer;某块事实变化时,把受影响邻居重新入队。当处理任何块都不再产生变化时,就到达不动点(fixed point)。
若域高度有限、transfer 单调,求解一定终止:事实只能沿格的允许方向移动,无法无限振荡。逆后序(reverse postorder)通常能加速向前分析,因为事实先穿过无环区域,再处理回边;向后分析常使用相反顺序。遍历顺序改变工作量,却不应改变正确最终解。
设计可复用求解器
通用 solver 应把方向、边界、meet、相等判断和 transfer 都显式参数化,不能把“向前 + 并集”写死在核心中。事实通常使用不可变值或可高效复制的 bitset;若意外原地修改前驱集合,多个块的结果可能一起被污染。
分析调试能力同样重要。实用工具应打印每个块的 IN、GEN/KILL 或 USE/DEF、OUT,以及事实在哪轮发生变化。菱形 CFG 和带回边的小 CFG 很适合 golden test。收敛后,把最终事实代回每条方程;每条方程都应精确再现存储结果。