前向分析(Forward Analysis): OUT[s]=fs(IN[s])
概述
前向分析,它是一种数据流分析技术,其中分析按照程序执行的顺序进行
在前向分析中,关注如何从程序的开头向结尾传播信息,并计算每个语句(或基本块)的输出值
OUT[s]=fs(IN[s]) 说明
- OUT[s]: 是语句s的输出数据流值,即执行语句s后得到的数据流
- IN[s]: 是语句s的输入数据流值,即进入语句s前已有的数据流信息
- fs: 转换函数,它根据语句s的具体内容来修改或更新输入的数据流值,生成输出的数据流值
例子
int a = 1;
int b = a + 2; // statement s1
int c = b; // statement s2
对于这段代码,可以进行以下前向分析
- 初始状态:
- IN[s1] 是空集,因为s1是程序的第一条语句
- OUT[s1] 尚未定义
- 应用转换函数fs到s1:
- OUT[s1] = fs(IN[s1]),这里 fs 会识别 a 在 s1 被赋值为1,因此OUT[s1] 中包含 a=1
- OUT[s1] 中包含 a=1
- s2 的输入是 s1 的输出:
- IN[s2] = OUT[s1],即 IN[s2] = {a=1
- 应用转换函数 fs 到 s2:
- OUT[s2] = fs(IN[s2]),这里 fs 会识别 b 在 s2 被赋值为 a + 2,但由于 a=1 是 s2 的输入数据流值,OUT[s2] 将包含 b=3 和 a=1(如果分析需要保留之前的值)
- 因此,OUT[s2] 是 {a=1, b=3}
反向分析(Backward Analysis): IN[s]=fs(OUT[s])
概述
“Backward Analysis”或“反向分析”是一种分析技术,它从程序或代码的输出结果开始,逆向推导出程序的状态或者输入值
这里的表达式“IN[s]=fs(OUT[s])”可以被解读为:给定一个状态s的输出OUT[s],通过某个函数fs来推导出该状态的输入IN[s]
IN[s]=fs(OUT[s]) 说明
- IN[s] 表示到达状态s之前的输入值或属性
- OUT[s] 表示从状态s出发的输出值或属性
- fs是某种函数或操作,它描述了如何从OUT[s] 得到 IN[s]
例子
假设我们有一个简单的表达式: a = b + c; 在编译器对这段代码进行反向分析时,可能会关注变量a的值如何由b和c计算得来的
- 假设我们已经知道a的值(输出, 即OUT[s])
- 反向分析要找出b和c的值(输入, 即IN[s])
如果我们知道a的值是5,并且表达式 a = b + c,那么反向分析可能如下:
假设fs是一个减法操作,那么我们可以写成fs(OUT[s]) = IN[s], 即:
- fs(5) = b + c
- 5 = b + c
- b = 5 - c
在这种情况下,如果我们知道c的值(比如2),我们就可以计算出b的值:
- b = 5 - 2
- b = 3
达成定义分析 (Reaching Definitions Analysis,may analysis)
概述
Reaching Definitions Analysis 是一种数据流分析技术,用于确定程序中某个变量定义对程序中其他点的可见性
Transfer Function:OUT[B] = genB U (IN[B] - killB)
OUT[B]: 这是分析中某基本块(或指令)B的输出集,表示从该基本块出发,哪些定义可以到达后续的基本块
genB: 这是由基本块B生成的定义集合。它包含了在B中直接为某个变量生成的所有定义
IN[B]: 这是进入基本块B的输入集,表示在执行到B之前,哪些定义是活跃的
killB: 这是在基本块B中被“杀死”的定义集合。如果一个定义在B中被另一个不同的定义覆盖了,那么它就被认为是被杀死了
转移函数 OUT[B] = genB U (IN[B] - killB) 的意思是:
-
- 首先,考虑在基本块B生成的所有新定义(genB)
-
- 然后,考虑所有进入基本块B的定义(IN[B])
-
- 从这些进入的定义中,移除在B中被杀死(覆盖)的定义(killB)
-
- 最后,将步骤1和步骤3的结果合并,得到基本块B的输出集(OUT[B]), 即从B出发到达后续基本块的定义集合
// 假设我们有以下数据结构
Set genB; // 在基本块B中生成的定义
Set IN[B]; // 进入基本块B的定义
Set killB; // 在基本块B中被杀死的定义
Set OUT[B]; // 从基本块B输出的定义
// 转移函数
OUT[B] = genB UNION (IN[B] DIFFERENCE killB);
Control Flow:IN[B] = Up a_predecesso_of_B Out[P]
IN[B]: 表示进入基本块B的活跃定义集合。即在执行基本块B之前,所有可能活跃的变量定义的集合
Out[P]: 表示从基本块P出发可以到达后续基本块的活跃定义集合
a_predecessor_of_B: 指的是基本块B的所有前驱(即所有可以跳转到B的基本块)
等式的意思,基本块B的入口定义集合(IN[B]) 是由基本所有前驱基本块P的出口定义集合(Out[p]) 合并而成的。换句话,为了计算基本块B的入口定义,需要查看所有能够到达B的基本块P,并取它们的Out[P]集合的并集
// 假设基本块B有一个或多个前驱基本块P1, P2, ...
IN[B] = Union(Out[P1], Out[P2], ...)
// 在迭代算法中,这可以表示为:
for each block B in CFG
for each predecessor P of B
IN[B] = IN[B] U Out[P]
最小上界(LUB,Join)
最小上界是指多个集合中能找到的最小的那个集合,它包含了所有这些集合的元素。在Reaching Definitions Analysi中,LUB用于合并两个控制流路径上的定义集合
计算过程上:
- 对于每个变量,收集两个路径上的定义集合
- 对比两个集合,取并集
- 这个并集就是这两个路径的最小上界
例如, 如果有两个路径P1 和 P2 对于变量x有以下定义集合:
- P1: {def1, def2}
- P2: {def3}
则它们的最小上界是{def1, def2, def3}
最大下界(GLB/meet)
最大下界是指多个集合中能找到的最大的那个集合,它是所有集合的公共元素。Reaching Definitions Analysis 中,GLB 用于处理控制流分支的汇聚点
计算过程如下:
- 对于每个变量,收集两个路径上的定义集合
- 对比两个集合,取交集
- 这个交集就这两个路径的最大下界
例如,如果有两个路径P1和P2对于变量x有以下定义集合:
- P1: {def1, def2}
- P2: {def2, def3}
最小上界(LUB,Join)& 最大下界(GLB/meet)代码例子
function lub(set1, set2):
return set1 ∪ set2
function glb(set1, set2):
return set1 ∩ set2
// 示例使用
setP1 = {def1, def2}
setP2 = {def2, def3}
lubResult = lub(setP1, setP2) // {def1, def2, def3}
glbResult = glb(setP1, setP2) // {def2}
活性变量分析 (Live Variables Analysis, may analysis)
Control Flow:OUT[B] = US a_successor_of_BIN[S]
OUT[B]: 基本块B的出口存活变量集合,即基本块B执行完毕后,接下来的执行仍然需要使用的变量集合
IN[a]: 基本块a的入口存活变量集合,即执行到基本块a时,需要使用的变量集合
US: 并集运算符,表示取所有后继基本块的入口存活变量集合的并集
a_successor_of_B: 表示基本块B的所有后继基本块
BB1:
t1 = a + b
if (c > 0) goto BB2 else goto BB3
BB2:
t2 = t1 * 2
...
BB3:
t3 = t1 - 1
...
在这个例子中,BB1有两个后继:BB2和BB3。
- 假设经过分析,我们知道在进入BB2时,变量t1和c是活的(因为t1在BB2中被使用,c用于条件跳转)。
- 同样,我们知道在进入BB3时,变量t1也是活的。
那么,对于BB1的出口存活变量集合OUT[BB1],我们会有:
OUT[BB1] = IN[BB2] ∪ IN[BB3]
= {t1, c} ∪ {t1}
= {t1, c}
这里,我们取了BB2和BB3的入口存活变量集合的并集,得到BB1的出口存活变量集合为t1和c
Transfer Function:IN[B] = useB U (OUT[B] - defB)
IN[B]: 进入基本块B的活跃变量集合
OUT[B]: 这是离开基本块B的活跃变量
useB: 这是在基本块B中使用的变量集合
defB: 这是在基本块B中定义(即赋值)的变量集合
转移函数的含义:
IN[B] = useB U (OUT[B] - defB) : 基本块B的输入活跃集合(IN[B]) 等于在基本块B中使用的变量(useB) 和不在基本块B中定义的输出活跃变量(OUT[B] - defB)的并集
例子:
B1:
x = a + b
y = c + d
if (condition) goto B2 else goto B3
B2:
print x
x = e + f
B3:
print y
在进行活跃变量分析时,假设condition可能为真或假,因为需要考虑两个路径
对于基本块B2, 我们有:
- useB2 = {x, e, f} (因为变量x,e, f在B2中被使用)
- defB2 = {x} (因为变量x在B2中被重新定义)
如果我们在进入B2之前知道OUT[B1],那么:
- OUT[B1] = {x, y} (因为x和y在if条件之后可能被使用)
根据转移函数,我们计算IN[B2]:
- IN[B2] = useB2 U (OUT[B1] - defB2)
- IN[B2] = {x, e, f} U ({x, y} - {x})
- iN[B2] = {x, e, f, y}
这意味着在进入B2时,变量x,e,f和y是活跃的。即使x在B2中被重新定义,它仍然是活跃的,因为它在B2中被使用了
最小上界(LUB)
最小上界是指在两个或多个集合中能找到的最小集合,它包含了所有这些集合的元素。在Live Variables Analysis中,LUB用于合并两个控制流路径上的活变量集合
计算步骤如下:
- 收集两个路径的活变量集合
- 对比两个集合,取它们的并集
- 结果就是这两个集合的最小上界
假设有两个活变量集合L1和L2,LUB的计算可以表示为:
LUB(L1, L2) = L1 ∪ L2
最大下界(GLB)
最大下界是指两个或多个集合中能找到的最大集合,它是所有集合的公共元素。在Live Variables Analysis中,GLB通常用于处理控制流分支的汇聚点
计算步骤如下:
- 收集两个路径的活变量集合
- 对比两个集合,取它们的交集
- 结果就是这两个集合的最大下界
假设有两个活变量集合L1 和 L2, GLB的计算可以表示为:
GLB(L1, L2) = L1 ∩ L2
在实际的数据流分析框架中,通常会使用半格(Semilattice) 这一数学工具辅助计算 LUB 和 GLB
可用表达式分析(Available Expressions Analysis, must analysis)
Transfer Function:OUT[B] = genB U (IN[B] - killB)
OUT[B]: 是基本块B的输出可用表达式集合,即离开基本块B仍然有效的表达式集合
genB: 是在基本块B中生成表达式集合,这些表达式在B中计算并且其值在B的末尾是已知的
IN[B]: 是进入基本块B的输入可用表达式集合,即到达基本块B之前已经计算并且其值仍然有效的表达式集合
killB: 是在基本块B中被“杀死”的表达式集合,即因为这些表达式中的变量在B中被重新赋值,所以它们在B的末尾不再有效
这个等式可以这样理解:
- IN[B] - killB 表示从输入集合中移除所有在当前基本块中被重复赋值的变量所对应的表达式
- genB U (IN[B] - killB) 表示将当前基本块中生成的表达式与调整的输入集合合并,形成输出集合
代码片段
a = b + c;
d = a + 1;
e = a + 1; // 这可能是公共子表达式
对应的三个基本块(为了简化,假设每行代码对应一个基本块):
- 基本块B1: a = b + c
- 基本块B2: d = a + 1
- 基本块B3: e = a + 1
分析过程如下:
对于基本块B1:
- genB1 = { a = b + c}
- killB1 = {} (因为a在B1中是第一次被赋值,没有表达式被杀死)
- OUT[B1] = {} (因为这是程序开始,没有输入表达式) 并上 genB1 = {a = b + c}
对于基本块B2:
- IN[B2] = OUT[B1] = {a = b + c}
- genB2 = {d = a + 1}
- killB2 = {a} (因为a在B2中被重新赋值)
- OUT[B2] = genB2 U (IN[B2] - killB2) = {d = a + 1} (注意 a = b + c 因为a被重新赋值而失效)
对于基本块B3:
- IN[B3] = OUT[B2] = {d = a + 1}
- genB3 = {e = a + 1}
- killB3 = {a} (因为a在B3中被重新赋值,但实际上这里没有改变输入集合,因为a在B2已经被杀死了)
- OUT[B3] = genB3 U (IN[B3] - killB3) = {d = a + 1, e = a + 1} (此时,由于a没有被使用,所以e = a + 1可以作为可用表达式)
Control Flow:IN[B] = ∩P a_predecessor_of_B OUT[P]
IN[B]:表示进入基本块B的可用表达式集合
OUT[P]:表示从基本块P出去的可用表达式集合
∩P a_predecessor_of_B:表示对所有P的交集,其中P是基本块B的所有前置基本块(即所有直接指向B的基本块)
例子:
1. a = b + c;
2. if (a > 10) {
3. d = a + 1;
4. } else {
5. d = a - 1;
6. }
7. e = a + d;
对应的基本块可以是:
- B1: a = b + c;
- B2: if (a > 10) {
- B3: d = a + 1;
- B4: } else {
- B5: d = a - 1;
- B6: }
- B7: e = a + d;
现在,我们来看基本块B7的输入可用表达式集合IN[B7]
B7的前置基本块是B3和B5,因为它们在控制流中直接指向B7
- OUT[B3] 可能包含 {d = a + 1, a = b + c}(假设在B3中没有其他对a和d的赋值)
- OUT[B5] 可能包含 {d = a - 1, a = b + c}(同样假设在B5中没有其他对a和d的赋值)
那么,IN[B7] = ∩{B3, B5} OUT[P] 将是两个集合的交集:
- {d = a + 1, a = b + c} ∩ {d = a - 1, a = b + c}
- 交集为 {a = b + c},因为d的值在两个分支中是不同的,所以在B7中不能确定d的确切值
因此,进入B7的可用表达式集合只包含 {a = b + c},这意味着在B7中,我们可以使用表达式 a = b + c 的值,但是不能确定d的值,因为d的值依赖于控制流