Bit-Vector框架(2) — Live Variables Analysis
以下是4个Bit-Vector Framework
的数据流分析经典例子。
- Reaching Definition Analysis:变量/数组在哪里最新被定义它们的值
Live Variables Analysis(本文)
:变量/数组的值在重新被定义之前(或者程序结束之前),是否被使用- Available Expressions Analysis:哪些表达式之前已经被计算过,当再次进行计算时能够得到相同的值
- Very Busy Expressions Analysis:哪些表达式在哪些点被计算后,程序结束前仍然被计算,并且计算的结果相等。
2. Live Variables
2.1 Live Variables简介
LV分析求解的信息
- 求解程序图中的
每个节点处
,哪些变量/数组
在它们被重新定义前(或者程序结束前)可能被使用。这些变量我们认为是活跃(live)变量,否则是死(dead)变量。
例子(图2.3 中)
- 在程序计算到q3处,之后就不再有变量x的使用。Dead(q3) = {x},Live(q3) = {y, q, r}
- 在程序计算到q6处,只有变量r的值需要被后续使用。Live(q6) = {r},Dead(q6) = {x, y, q}
LV求解信息的用途
- 在
编译器构造
中很有用。比如:一个变量存储在寄存器中,LV的信息可以确定是否该寄存器(该变量)可能会被重用(reuse)作其它用途(没有的话,我们可以直接随意覆盖该寄存器的值,以节约寄存器资源)。 编译器构造和程序理解
:程序中执行某些计算,在程序的后续执行中从没有使用过。(这种问题可能出现在一个被多个程序员维护的程序中)安全
: 检测变量违规使用:本意是只让某个变量被某次使用后就不再使用,但是通过LV信息得到它仍然还在使用。
2.2 分析的结果定义
定义LV函数,它映射每个程序点到一个变量/数组的集合。
L
V
:
Q
→
P
o
w
e
r
S
e
t
(
(
V
a
r
∪
A
r
r
)
)
LV: Q \rightarrow PowerSet(\ \ (Var \cup Arr)\ \ )
LV:Q→PowerSet( (Var∪Arr) )
LV映射程序图中每个节点到一个变量/数组的集合。
在图2.3中,我们知道
Q
=
{
q
0
,
q
1
,
q
2
,
q
3
,
q
4
,
q
5
,
q
6
,
q
x
}
Q = \{q_0, q_1, q_2, q_3, q_4, q_5, q_6, q_x\}
Q={q0,q1,q2,q3,q4,q5,q6,qx}
而求解出来的LV应该满足如下条件(后续会介绍怎么一步步求解LV函数):
L
V
(
q
0
)
⊇
{
x
,
y
}
L
V
(
q
1
)
⊇
{
x
,
y
}
L
V
(
q
2
)
⊇
{
x
,
y
,
q
}
L
V
(
q
3
)
⊇
{
y
,
q
,
r
}
L
V
(
q
4
)
⊇
{
y
,
q
,
r
}
L
V
(
q
5
)
⊇
{
y
,
q
,
r
}
L
V
(
q
6
)
⊇
{
r
}
L
V
(
q
x
)
⊇
{
}
\begin{aligned} LV(q_0) \ & \ \supseteq \{x, y\} \\ LV(q_1) \ & \ \supseteq \{x, y\} \\ LV(q_2) \ & \ \supseteq \{x, y, q\} \\ LV(q_3) \ & \ \supseteq \{y, q, r\} \\ LV(q_4) \ & \ \supseteq \{y, q, r\} \\ LV(q_5) \ & \ \supseteq \{y, q, r\} \\ LV(q_6) \ & \ \supseteq \{r\} \\ LV(q_x) \ & \ \supseteq \{\} \\ \end{aligned}
LV(q0) LV(q1) LV(q2) LV(q3) LV(q4) LV(q5) LV(q6) LV(qx) ⊇{x,y} ⊇{x,y} ⊇{x,y,q} ⊇{y,q,r} ⊇{y,q,r} ⊇{y,q,r} ⊇{r} ⊇{}
右侧的集合在任何情况下(不管程序如何执行)都满足,并且这个集合是满足条件的最小集合,那么我们就认为这是最好/最小的解。
2.3 路径上使用了什么
2.3.1 定义程序单个行为的语义函数
首先分析对于每个行为act,那个变量/数组会被使用:
U
s
e
(
x
:
=
a
)
=
f
v
(
a
)
U
s
e
(
A
[
a
1
]
=
a
2
)
=
f
v
(
a
1
)
∪
f
v
(
a
2
)
U
s
e
(
c
?
x
)
=
{
}
U
s
e
(
c
?
A
[
a
]
)
=
f
v
(
a
)
U
s
e
(
c
!
a
)
=
f
v
(
a
)
U
s
e
(
b
)
=
f
v
(
b
)
U
s
e
(
s
k
i
p
)
=
{
}
\begin{aligned} &Use(x := a) &&= fv(a) \\ &Use(A[a_1]=a_2) &&= fv(a1) \ \cup \ fv(a2) \\ &Use(c?x) && = \{\} \\ &Use(c?A[a]) &&= fv(a) \\ &Use(c!a) &&= fv(a) \\ &Use(b) &&= fv(b) \\ &Use(skip) &&= \{\} \\ \end{aligned}
Use(x:=a)Use(A[a1]=a2)Use(c?x)Use(c?A[a])Use(c!a)Use(b)Use(skip)=fv(a)=fv(a1) ∪ fv(a2)={}=fv(a)=fv(a)=fv(b)={}
对于一个表达式a,我们使用一个函数fv(a)表示表达式a中所使用到的变量集合
,例如,a: x + y, 其中x,y为简单变量,那么fv(a) = {x, y}
下面给出fv函数定义:
f
v
(
n
)
=
{
}
n
为
整
数
字
面
量
f
v
(
x
)
=
{
x
}
x
为
简
单
变
量
f
v
(
A
[
a
0
]
)
=
{
A
}
∪
f
v
(
a
0
)
f
v
(
a
1
O
P
a
a
2
)
=
f
v
(
a
1
)
∪
f
v
(
a
2
)
f
v
(
−
a
0
)
=
f
v
(
a
0
)
f
v
(
t
r
u
e
)
=
{
}
f
v
(
f
a
l
s
e
)
=
{
}
f
v
(
a
1
O
P
r
a
2
)
=
f
v
(
a
1
)
∪
f
v
(
a
2
)
f
v
(
b
1
O
P
b
b
2
)
=
f
v
(
b
1
)
∪
f
v
(
b
2
)
f
v
(
b
0
)
=
f
v
(
b
0
)
\begin{aligned} &fv(n) &&= \{\} && n为整数字面量\\ &fv(x) &&= \{x\} && x为简单变量\\ &fv(A[a0]) &&= \{A\} \ \cup \ fv(a0) && \\ &fv(a1 \ \ OPa \ \ a2) &&= fv(a1) \ \cup \ fv(a2) \\ &fv(-a0) &&= fv(a0) \\ &fv(true) &&= \{\} \\ &fv(false) &&= \{\} \\ &fv(a1 \ \ OPr \ \ a2) &&= fv(a1) \ \cup \ fv(a2) \\ &fv(b1 \ \ OPb \ \ b2) &&= fv(b1) \ \cup \ fv(b2) \\ &fv(\text{~}b0) &&= fv(b0) \end{aligned}
fv(n)fv(x)fv(A[a0])fv(a1 OPa a2)fv(−a0)fv(true)fv(false)fv(a1 OPr a2)fv(b1 OPb b2)fv( b0)={}={x}={A} ∪ fv(a0)=fv(a1) ∪ fv(a2)=fv(a0)={}={}=fv(a1) ∪ fv(a2)=fv(b1) ∪ fv(b2)=fv(b0)n为整数字面量x为简单变量
例如:
fv(x * y) = fv(x)
∪ \cup ∪fv(y) = {x}
∪ \cup ∪{y} = {x, y}
fv(A[i] = x) = fv(i)
∪ \cup ∪fv(x) = {i}
∪ \cup ∪{x} = {i, x}
2.3.2 一条路径上变量x的Use函数
对于程序中的一条路径Pi:
P
i
=
q
0
,
a
1
,
q
1
,
a
2
,
q
2
,
.
.
.
,
q
n
−
1
,
a
n
,
q
n
其
中
,
n
≥
0
q
i
∈
Q
,
0
≤
i
<
n
a
j
∈
A
c
t
,
1
≤
j
≤
n
\begin{aligned} Pi = &\ q_0, a_1, q_1, a_2, q_2, ..., q_{n-1}, a_n,q_n \\ 其中,&n \geq 0 \\ &q_i \in Q, 0 \leq i < n\\ &a_j \in Act, 1 \leq j \leq n \end{aligned}
Pi=其中, q0,a1,q1,a2,q2,...,qn−1,an,qnn≥0qi∈Q,0≤i<naj∈Act,1≤j≤n
我们定义一条路径Pi上对于变量x的Use函数为:
U s e ( P i , x ) = ∃ i : x ∈ U s e ( a i ) ∧ ∀ j < i : x ∉ D e f ( a j ) Use(Pi, x) = \exist i:x\in Use(a_i) \wedge \forall j < i: x \notin Def(a_j) Use(Pi,x)=∃i:x∈Use(ai)∧∀j<i:x∈/Def(aj)
Use(Pi, x)表示对于一条路径Pi,变量x是否被使用。Use(Pi, x)的true/false与否取决于,是否:
路径中存在某个行为a,使得x被使用;并且在该路径中a前面所有路径节点中,不存在任何行为使得x被定义。
也就是说,路径上使用了未被定义过的变量x,那么我们就认为这条路径使用过变量x,记作
Use(Pi, x) = true
例如,对于图2.3中的路径Pi:
P
i
=
{
q
0
,
x
>
=
0
∧
y
>
0
,
q
1
,
q
:
=
0
,
q
2
,
r
:
=
x
,
q
3
,
r
<
y
,
q
6
,
o
u
t
!
r
,
q
x
}
Pi = \{q_0, \ x>=0 \wedge y>0, \ q_1, \ q:=0, \ q_2, \ r:=x, \ q_3, r<y, \ q_6, \ out!r, \ q_x\}
Pi={q0, x>=0∧y>0, q1, q:=0, q2, r:=x, q3,r<y, q6, out!r, qx}
-
Use(Pi, x) = true
-
Use(Pi, y) = true
-
Use(Pi, q) = false
-
Use(Pi, r) = false
2.3.3 一条路径上数组A的Use函数
而对于数组变量,Use(Pi, A)函数的定义为:
U
s
e
(
P
i
,
A
)
=
∃
a
i
:
A
∈
U
s
e
(
a
i
)
Use(Pi, A) = \exist a_i: A \in Use(a_i)
Use(Pi,A)=∃ai:A∈Use(ai)
这里对数组作保守分析,只要路径上使用了数组A,那么我们就认为该路径使用了数组A
2.3.4 总结
综上,对于一条路径Pi,它的Use函数Use(Pi)定义了该路径使用到的变量/数组的集合:
U
s
e
(
P
i
)
=
{
x
∈
V
a
r
∣
U
s
e
(
P
i
,
x
)
=
t
r
u
e
}
∪
{
A
∈
A
r
r
∣
U
s
e
(
P
i
,
A
)
=
t
r
u
e
}
Use(Pi) = \{x \in Var \ | \ Use(Pi, x)=true \} \ \cup \ \{A \in Arr \ | \ Use(Pi, A) = true\}
Use(Pi)={x∈Var ∣ Use(Pi,x)=true} ∪ {A∈Arr ∣ Use(Pi,A)=true}
通过上面的概念,我们能够重新定义Live Variables Analysis的目的:
对于程序图上每个节点q,我们想要求解出从节点q开始,到节点qx的所有路径中,将要被使用到的所有变量/数组的集合。
LV分析的结果总结了(summaries)程序图上每条从节点q到节点qx的路径Pi,使得Use(Pi) ⊆ \subseteq ⊆ LV(q)。
能够总结出程序点q的所有从q到qx路径的最小(least)分析结果,我们也称之为
MOP解(Merge Over Paths solution)
2.4 LV分析的约束
2.3是通过将所有路径的定义结果进行merge合并,这并不是一种有效的计算正确的RD分析的方法。因为很多时候我们静态地并不能够分析出所有的可能执行路径,所以很难将所有路径的结果进行merge。
为了改进上述方法。现在,我们定义一个约束系统,LV函数必须满足,之后我们会给出算法来求解这些约束。
在定义约束系统之前,我们先定义一下约束系统中的约束因子:KILL和GEN
对于程序图中每条边(qs, a, qt)
,我们定义所谓的对Live Variables的KILL函数和GEN函数:
a | KILL(q s _s s, a, q t _t t) | GEN(q s _s s, a, q t _t t) | 解释 |
---|---|---|---|
x := a | {x} | fv(a) | 1. 此边重定义x,x到此为止不是live variable |
A[a1] := a2 | {} | fv(a1) ∪ \cup ∪ fv(a2) | 2. 保守分析,不Kill掉数组;生成新的Live Variables |
c?x | {x} | {} | 同1 |
c?A[a] | {} | fv(a) | 同2 |
c!a | {} | fv(a) | 3. 使用到表达式a,生成fv(a) |
b | {} | fv(b) | 同3 |
Skip | {} | {} | 无语义 |
KILL,GEN的目的是为RD分析的约束服务。现在我们定义约束:
对于程序图上的每条边edge(qs, a, qt),必须满足:
∀ ( q s , a , q t ) ∈ E d g e L V ( q s ) ⊇ ( L V ( q t ) − K I L L ( q s , a , q t ) ) ∪ G E N ( q s , a , q t ) \begin{aligned} \forall (q_s, a, q_t)\ &\in \ Edge \\ LV(q_s) \ &\supseteq \ (\ LV(q_t) - KILL(q_s, a, q_t) \ ) \ \cup \ GEN(q_s, a, q_t) \end{aligned} ∀(qs,a,qt) LV(qs) ∈ Edge⊇ ( LV(qt)−KILL(qs,a,qt) ) ∪ GEN(qs,a,qt)
对于程序结束点qx,额外要求:
L V ( q x ) ⊇ { } LV(q_x) \supseteq \{\} LV(qx)⊇{}
前者表示,从边(qs, a, qt)的终点qt传递过来的Live variables集合可能会通过该边的a行为kill掉部分live variables。于此同时a行为又会产生live variables。
约束条件中存在一个子集的概念,也就是说,无论qs的后继节点是哪个qt,右侧求出的集合一定是LV(qs)的子集。因此,可以这么认为:对qs的所有后继qt,求解右侧的集合结果运算,并将这些计算并集,就会得到LV(qs)。
后者约束条件默认为空。当然我们也能指定某些变量,用于表示我们对某个变量感兴趣(让它在程序终点处于Live状态,即使它是dead状态,我们也强制认为它是Live状态)。如 L V ( q x ) ⊇ { y } {LV(q_x) \supseteq} \{y\} LV(qx)⊇{y} 表示我们对变量y感兴趣,它在程序终点处于Live状态。(比如:我们不想要让y变量所在的寄存器被重用)
下面是一个简单的例子:
根据上述约束,我们重新定义一下Live Variables Analysis的目的:
LV分析就是根据约束来求解出每个程序点的LV函数值,上述约束要在任何时候(等同于任何执行路径)都满足
2.5 求解约束
输 入 : 集 合 Q 包 含 程 序 图 中 所 有 节 点 ; 程 序 结 束 节 点 q x ; 边 集 合 E 输 出 : L V : L i v e V a r i a b l e s A n a l y s i s 分 析 的 结 果 函 数 算 法 : ∀ q ∈ Q , 让 L V ( q ) = { } W h i l e 仍 然 存 在 ( q s , a , q t ) ∈ E , 使 得 L V ( q s ) ⊉ ( L V ( q t ) − K I L L ( q s , a , q t ) ) ∪ G E N ( q s , a , q t ) 那 么 令 L V ( q s ) : = L V ( q s ) ∪ ( L V ( q t ) − K I L L ( q s , a , q t ) ) ∪ G E N ( q s , a , q t ) \begin{aligned} 输入:&集合Q包含程序图中所有节点;程序结束节点q_x;边集合E \\ 输出:&LV:Live \ Variables \ Analysis分析的结果函数 \\ 算法:&\\ &\forall q \in Q,让LV(q) = \{\} \\ &While \ 仍然存在(q_s, a, q_t) \in E,使得LV(q_s) \not\supseteq (\ LV(qt)-KILL(q_s, a, q_t) \ ) \ \cup \ GEN(q_s, a, q_t) \\ & \ \ \ \ \ \ \ \ \ \ \ \ 那么令LV(qs) := LV(qs) \ \cup \ (\ LV(qt)-KILL(q_s, a, q_t) \ ) \ \cup \ GEN(q_s, a, q_t) \end{aligned} 输入:输出:算法:集合Q包含程序图中所有节点;程序结束节点qx;边集合ELV:Live Variables Analysis分析的结果函数∀q∈Q,让LV(q)={}While 仍然存在(qs,a,qt)∈E,使得LV(qs)⊇( LV(qt)−KILL(qs,a,qt) ) ∪ GEN(qs,a,qt) 那么令LV(qs):=LV(qs) ∪ ( LV(qt)−KILL(qs,a,qt) ) ∪ GEN(qs,a,qt)
此算法求解约束,得到最小分析结果,我们也称之为MFP解(Minimal Fixed Point solution)