Archipelago

Lec 8数据流分析(Dataflow Analysis)

讲义完成更新于 2026-09-16

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 数据流方程与不动点求解

IN[b]=⋃p∈pred(b)OUT[p]\text{IN}[b] = \bigcup_{p \in \text{pred}(b)} \text{OUT}[p] OUT[b]=(IN[b]−KILL[b])∪GEN[b]\text{OUT}[b] = (\text{IN}[b] - \text{KILL}[b]) \cup \text{GEN}[b] IN[entry]=∅\text{IN}[\text{entry}] = \varnothing

得到一个方程组,用不动点算法 (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 在点 pp 可用,当且仅当:① 从初始节点到 pp 的每条路径都在到达 pp 前求过 x+y;② 求值之后、到 pp 之前,x、y 都未被重新赋值。

可用表达式信息支持全局 CSE:若某表达式在使用点可用,无需重新求值(须在所有相关块用同一个临时变量)。

3.1 与到达定义的对偶(关键区别)

定理(汇合算子的对偶)

  • 到达定义:定义只要来自任一 (ANY) 前驱就到达 ⟹ 汇合算子是并 ∪,OUT 初始化为 ∅。

  • 可用表达式:表达式须从所有 (ALL) 前驱都可用才可用 ⟹ 汇合算子是交 ∩,OUT 初始化为全集 E。 :::

方程:

IN[b]=⋂p∈pred(b)OUT[p],OUT[b]=(IN[b]−KILL[b])∪GEN[b]\text{IN}[b] = \bigcap_{p \in \text{pred}(b)} \text{OUT}[p], \qquad \text{OUT}[b] = (\text{IN}[b] - \text{KILL}[b]) \cup \text{GEN}[b]

求解时把 OUT[b] 初始化为全集 1111,IN[entry]=∅,同样用工作表迭代到不动点。

这揭示了通用数据流框架:只需把分析器参数化(方向、汇合算子、初值、GEN/KILL/转移函数),一次构建、处处复用。


4. 活跃性分析(Liveness Analysis)

变量 vv 在点 pp 活跃 (live),当且仅当:从 pp 出发的某条路径上有对 vv 的使用,且使用前路径上没有对 vv 的定义。反之,若从 pp 到出口的任何路径都不再使用 vv(或都在使用前重定义 vv),则 vv 在 pp 死 (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): OUT[b]=⋃s∈succ(b)IN[s],IN[b]=USE[b]∪(OUT[b]−DEF[b])\text{OUT}[b] = \bigcup_{s \in \text{succ}(b)} \text{IN}[s], \qquad \text{IN}[b] = \text{USE}[b] \cup (\text{OUT}[b] - \text{DEF}[b])

例题(活跃性驱动 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 / 死代码消除与寄存器分配。
  • 通用框架:参数化方向、汇合算子、初值与转移函数,一次构建处处复用;乐观/悲观取决于用途。