Lec 8数据流分析(Dataflow Analysis)
Lec 08 数据流分析(Dataflow Analysis)
配套复习课:R9 Phase 4 + GDB 速成(见末节)——数据流分析正是 Phase 4 项目 参考:把 L7 基本块内分析推广到跨基本块、全过程 (global)
TL;DR
- 数据流分析在 CFG 上为每个程序点求事实,用 IN / OUT / GEN / KILL 方程和工作表算法迭代到不动点。
- 到达定义是前向、并集问题;可用表达式是前向、交集问题;活跃性是后向、并集问题。
- 分析结果为常量传播、CSE、死代码消除和寄存器分配提供合法性与收益判断所需的跨块信息。
1. 从基本块到全过程
L7 在基本块内做分析与变换;本讲把它推广到整个过程。三个经典数据流问题贯穿全讲:到达定义 (Reaching Definitions)、可用表达式 (Available Expressions)、活跃性 (Liveness)。
2. 到达定义(Reaching Definitions)
定义(定义、使用、到达)
a = x+y 是对 a 的一个定义 (definition),是对 x、y 的使用 (use)。一个定义到达 (reaches) 某使用,当且仅当该定义写入的值可能被该使用读到。
2.1 用途:常量传播
例题(到达定义驱动常量传播)
对 s = s + a*b:检查变量的所有到达定义。
-
a:唯一到达定义a=4⟹ 可替换为s = s + 4*b。 -
b:有两个到达定义(一支b=1,一支b=2)⟹ 非常量,不可替换。
分裂 (splitting) 可保住汇合处丢失的信息:把汇合后的块按前驱复制,则两份分别可做 a*1、a*2 的常量替换。
2.2 形式化:IN/OUT/GEN/KILL
用位向量 (bit vector) 表示定义集合,每个定义占一位。每个基本块有:
定义(数据流四集合)
-
IN[b]:到达块开头的定义集;OUT[b]:到达块结尾的定义集;
-
GEN[b]:块内生成的定义集;KILL[b]:块内杀死的定义集。
例:GEN[s=s+a*b; i=i+1;] = 0000011,KILL[...] = 1010000(杀死其他对 s、i 的定义)。
2.3 数据流方程与不动点求解
得到一个方程组,用不动点算法 (fixed-point algorithm) + 工作表 (worklist) 求解:
for all n: OUT[n] = ∅
IN[Entry]=∅; OUT[Entry]=GEN[Entry]; Changed = N - {Entry}
while Changed ≠ ∅:
取 n ∈ Changed; Changed -= {n}
IN[n] = ⋃_{p∈pred(n)} OUT[p]
OUT[n] = GEN[n] ∪ (IN[n] - KILL[n])
if OUT[n] 改变: 把 n 的后继全部加入 Changed
定理(算法终止性)
算法必然停机,因为转移函数单调 (monotonic)——增大 IN 则 OUT 不减;位向量上界有限(极限时所有位为 1),故有限步到达不动点。
保守性提醒:若某位为 0,对应定义确实从不到达该块;若为 1,对应定义未必总到达(可能某些路径到达即置 1)。即分析是"可能 (may)"语义。
3. 可用表达式(Available Expressions)
定义(可用表达式)
表达式 x+y 在点 可用,当且仅当:① 从初始节点到 的每条路径都在到达 前求过 x+y;② 求值之后、到 之前,x、y 都未被重新赋值。
可用表达式信息支持全局 CSE:若某表达式在使用点可用,无需重新求值(须在所有相关块用同一个临时变量)。
3.1 与到达定义的对偶(关键区别)
定理(汇合算子的对偶)
-
到达定义:定义只要来自任一 (ANY) 前驱就到达 ⟹ 汇合算子是并 ∪,OUT 初始化为 ∅。
-
可用表达式:表达式须从所有 (ALL) 前驱都可用才可用 ⟹ 汇合算子是交 ∩,OUT 初始化为全集 E。 :::
方程:
求解时把 OUT[b] 初始化为全集 1111,IN[entry]=∅,同样用工作表迭代到不动点。
这揭示了通用数据流框架:只需把分析器参数化(方向、汇合算子、初值、GEN/KILL/转移函数),一次构建、处处复用。
4. 活跃性分析(Liveness Analysis)
变量 在点 活跃 (live),当且仅当:从 出发的某条路径上有对 的使用,且使用前路径上没有对 的定义。反之,若从 到出口的任何路径都不再使用 (或都在使用前重定义 ),则 在 死 (dead)。
用途:寄存器分配(变量死了可重用其寄存器);死代码消除(删除后续不读的赋值——但不能删除对外部可见变量的最后一次赋值,做法是让所有外部可见变量在 CFG 出口处活跃)。
4.1 逆向分析
定义(活跃性的四集合与方程)
-
IN[b]:块开头活跃的变量;OUT[b]:块结尾活跃的变量;
-
USE[b]:块内"向上暴露使用 (upwards exposed use)"的变量;DEF[b]:块内被定义的变量。
例:USE[x=z; x=x+1;] = {z}(x 不在 USE,因首次出现是被赋值前的右侧 z;x 在用前已被定义);DEF[x=z; x=x+1; y=1;] = {x, y}。
方程(逆向,沿后继求 OUT):
例题(活跃性驱动 DCE,位向量序 abcxyzt)
假设 a、b、c 对外可见(出口处活跃),x、y、z、t 不可见。逆向传播活跃位向量;对 t = a; 之类,若 t 在其后不活跃,则该赋值为死代码,删除。
5. 三大分析的统一与比较
定理(数据流分析的统一框架)
三者都是"工作表迭代到不动点",区别仅在四个参数:
| 分析 | 方向 | 汇合算子 | OUT/IN 初值 | 配套变换 |
|---|---|---|---|---|
| 到达定义 | 前向 | ∪ | ∅ | 常量传播 |
| 可用表达式 | 前向 | ∩ | 全集 E | 全局 CSE |
| 活跃性 | 逆向 | ∪ | ∅ | 死代码消除 / 寄存器分配 |
都有"转移函数 + 汇合算子",框架可推广同时支持前向与逆向。
5.1 块内信息
给定块 IN/OUT 后,还需算块内每条语句处的信息——简单传播即可,可视为受限的数据流分析。
5.2 乐观 vs. 悲观分析
定义(乐观与悲观)
-
可用表达式是乐观的(为 CSE):初始假设所有表达式都可用,分析逐步剔除不可用者;不能提前停止用中间结果(中途结果不安全)。
-
活跃变量是悲观的(为 DCE):初始假设所有变量都活跃,分析逐步找出死变量;可提前停止用当前结果(当前结果始终安全)。
数据流设置相同,乐观/悲观取决于预期用途。
6. 补充(R9):Phase 4 与 GDB 速成
数据流分析正是项目 Phase 4。调试生成代码常用 GDB:
- 编译带调试信息(汇编/可执行带符号),
gdb ./prog启动; break <label/行>设断点,run [args]运行,continue/next/step控制;info registers看寄存器、x/8xg $rsp检查栈内存、print $rax看返回值寄存器;disassemble看当前函数汇编,stepi/nexti单步机器指令——对调试代码生成器尤为关键;backtrace看调用栈,配合段错误定位活动记录/调用约定的问题。
我的理解
分析的方向和汇合算子不应靠背诵。先问「事实是由前驱还是后继决定」,再问「所有路径成立还是任一路径成立」,就能推出前 / 后向以及交 / 并集。
7. 经典数据流问题小结
- 把基本块分析推广到全过程:CFG 上每块的 IN/OUT,由 GEN/KILL(或 USE/DEF)经转移函数与汇合算子联立成方程组,工作表迭代到不动点(转移单调保证终止)。
- 三大问题对偶:到达定义(前向、∪、∅)/可用表达式(前向、∩、全集)/活跃性(逆向、∪、∅)。
- 配套变换:常量传播 / 全局 CSE / 死代码消除与寄存器分配。
- 通用框架:参数化方向、汇合算子、初值与转移函数,一次构建处处复用;乐观/悲观取决于用途。