本文旨在总结常见数据流分析:活跃性(liveness)分析,常用表达式(Available Expressions)分析,very busy 表达式(VeryBusy Expressions)分析,定值到达(Reaching Defs)分析,文章中的各类概念定义并不是严格定义,主要是帮助自己理解。
数据流分析
数据流分析是编译器,程序分析,垃圾回收等技术的公共基础,数据流分析的直觉感受可以认为是,在程序的控制流图上(CFG),将每条语句视为CFG上的节点(又叫程序点),计算出节点前后的数据流信息(liveness, Reaching Defs,VeryBusy Expressions,Available Expressions)的情况,即节点前驱或后继的边上的信息,仿佛数据流信息在CFG上的边上“流动“”。
值得注意的是每种数据流分析重要的关注点有以下共同几点:
1 . 转移函数 (transfer function): 表示节点前后数据流信息关系的方程
2 . 汇集函数(Merging Function): 在程序分支点(条件语句,while语句)(convergence)前后的数据流信息的关系
3. 迭代分析方向,前向或者后向
4 . 算法初始值
个人理解:
- 数据流分析时,每种数据流计算的信息是不一样的,脑海中一定要清楚:经过程序点后,那些是新生成的信息(gen),那些旧的信息需要删除(kill),这些gen-kill信息一定会在转移函数上体现。
1. 活跃性分析
1.1 概念定义
if a variable is used at a program point p, then it must be alive immediately before p.
翻译:一个变量v在程序点p使用,那么在程序点p之前,它都是活跃的。
补充理解:
一个变量v从定义点到使用点,只要中间没有重定义x,x的生命周期就是定义点到使用点,
示例代码:
var x,y,z;
x = 2; (定义点)
...... // n条语句,但不包括对x的赋值语句
z = x-4; (使用点)
//在使用点之前,x一定是活跃的,变量的生命周期从def到use,是左开右闭(def,use].
1.2 数据流分析方程
p : v = E
IN ( p ) = (OUT( p )
∖
\setminus
∖ {v} )
∪
\cup
∪ vars(E) (transfer function)
OUT ( p) =
∪
\cup
∪ IN(
p
s
p_s
ps) ,
p
s
p_s
ps
∈
\in
∈ succ( p ) (merging function)
- IN ( p ) : 经过程序点之前活跃变量的集合
- OUT( p ) :经过程序点之后活跃变量的集合
- vars(E) :表达式中使用的变量
- succ( p ):在CFG中,程序点p的后继节点
解释:
- 每个程序点有两个集合, IN ( p ) 和OUT ( p) ,代表了经过程序点之前和之后活跃变量的集合。
- 转移函数:
- 首先从等式可以看出,它是从OUT信息推出IN信息,所以它是一个Backward(后向)分析,
- 另外,等式中的差运算 {v},表示等式左边的{v}, 活跃性在程序点p杀死了,需要删除(kill);并运算vars(E),程序点右边表达式使用的变量,在程序点之前一定是活跃的(Gen),所以加入集合IN ( p )。
- 汇集函数: 汇集函数对于后向分析,比较关注多个后继节点IN ( p ) 对该节点OUT( p )的影响,因为liveness它是一个may的信息,所以汇集函数使用并运算。
1.3 用途
- 活跃性分析最重要的一个优化就是寄存器分配,该优化关心每个变量的生命周期,从体系结构最基本的知识,我们知道寄存器是处理器上读写速度最快的资源,寄存器分配要确保最经常使用的变量放在寄存器,不经常使用的放入内存,这部分的优化也是另外一个复杂问题,在我博客寄存器分配算法有相关笔记。
- 活跃性分析,可以编译报错阶段,找到未定义的变量,输出debug信息。
2. 定值到达分析
2.1 概念定义
if a program point p defines a variable v, then v reaches the point immediately after p.
如果程序点p定义了一个变量v,那么变量v的定义可以到达p以后的程序点。
补充理解:
对变量的赋值在编译器术语中又叫定值,在赋值号右边的变量叫变量的使用,定值到达分析,关心赋值语句有没有到达它的使用点, 例如下面的代码片段,x的定义有两个点,我们比较关心到底哪个的赋值语句,才是真正对use有用的。
示例代码:
var x,y,z;
x = 2; (定义点1)
...... // n条语句,但不包括对x的赋值语句
x = 4; (定义点2)
...... // n条语句,但不包括对x的赋值语句
z = x-4; (使用点)
2.2 数据流方程
p : v = E
OUT ( p) = (IN ( p )
∖
\setminus
∖ {defs(v)} )
∪
\cup
∪{p} (transfer function)
IN ( p ) =
∪
\cup
∪ OUT(
p
s
p_s
ps) ,
p
s
p_s
ps
∈
\in
∈ pred( p ) (merging function)
- IN ( p ) : 经过程序点之前到达定值(reaching-defs)集合
- OUT( p ) :经过程序点之后到达定值(reaching-defs)集合
- defs(E) :表达式中的到达定值,
- pred( p ):在CFG中,程序点p的前驱节点
解释:
- 每个程序点有两个集合, IN ( p ) 和OUT ( p) ,代表了经过程序点之前和之后到达定值的集合。
- 转移函数:
- 首先从等式可以看出,它是从IN 信息推出OUT信息,所以它是一个Forward(前向) 分析。
- 另外,等式中的差运算 {v},表示等式左边的{v}的到达定值在程序点p杀死了,需要删除(kill);并运算{p} , 程序点新生成的defs需要加入集合,这里有个小技巧,直接将defs等价于程序点,所以是并运算程序点。
- 汇集函数: 汇集函数对于前向分析,比较关注多个前驱节点的OUT ( p)对该节点的IN ( p ) 的影响,因为Available Expression它是一个may的信息,所以汇集函数使用并运算。
2.3 用途
小结:
- 到达定值和活跃性分析,感觉上似乎很像,但是这两个分析从实例代码可以看出,它们的关注点是不同的,活跃性关心,变量从使用点,倒推回定义点,关心这个变量的生命周期;而定值到达分析,关心程序点的defs可以走多远。
- 到达定值分析可以做一些死代码删除,不过值得提出的是,在程序中间表示有SSA形式以后,活跃性分析和定值到达分析直接在ssa形式上表现出来了,详见我的博文SSA生成算法。
3. 可用表达式分析
3.1 概念定义
if an expression is used at a point p, then it is available immediately after p, as along as p does not redefine any the variables that the expression uses.
翻译:如果一个表达式在程序点p使用(出现),那么在程序点p以后,它都是可用的,只要程序点p没有重定义表达式使用的变量。
补充理解:
如果一个表达式在多个程序点出现,我们自然会想到,它们是不是相同的,因为如果是相同的话,只需计算一次表达式的值,后面的程序点直接使用该值就可以了。如下面的例子,程序点1,2,3都用了相同的表达式(a+b), 唯一的区别就是程序点12之间没有更改表达式里面变量的值,而程序点23之间,变量a的值改变了.
示例代码:
var x,y,z,a,b;
x = a+b; // 程序点 1
y = a*b;
if (y > a+b){ // 程序点 2
a = a+1
x = a+b; // 程序点 3
}
3.2 数据流方程
p : v = E
OUT ( p) = (IN ( p )
∪
\cup
∪ {E} )
∖
\setminus
∖ {Expr(v)} (transfer function)
IN ( p ) =
∩
\cap
∩ OUT(
p
s
p_s
ps) ,
p
s
p_s
ps
∈
\in
∈ pred( p ) (merging function)
- IN ( p ) : 经过程序点之前可用表达式(available expressions)集合
- OUT( p ) :经过程序点之后可用表达式(available expressions)集合
- Expr(v) :含变量v的可用表达式集合
- pred( p ):在CFG中,程序点p的前驱节点
解释:
- 转移函数:
- 首先从等式可以看出,它是从IN 信息推出OUT信息,所以它是一个Forward(前向) 分析。
- 另外,并运算 {E} , 表示程序点使用的表达式需要加入集合;等式中的差运算Expr(v),表示,需要删除(kill)含变量v的可用表达式;
- 汇集函数: 汇集函数对于前向分析,比较关注多个前驱节点的OUT ( p)对该节点的IN ( p ) 的影响,因为Available Expression它是一个Must的信息,所以汇集函数使用交运算。
值得注意的是条件表达式也是表达式,需要加入计算,而活跃性分析与定制到达分析只关心define的表达式,不包含条件表达式计算。
3.3 分析用途
条件表达式分析之后的优化可以做一些表达式化简相关的操作。
4.VeryBusy 表达式分析
4.1 概念定义
if an expression E is very busy at a program point if it will be computed before the program terminates along any path that goes from that point to he end of the program.
补充理解:
非常忙(verybusy)表达式是可用表达式一种特殊情况,当某个表达式在控制流图的各个分支中都有出现,我们针对这个表达式做一些化简操作,但是如何得到这个VeryBusy的表达式呢?从后向前倒退出各个程序点前后的可用表达式信息,如果在各个分支都有出现的表达式,后面就可以做优化操作,强调一下,Verybusy分析,只是从后往前分析出各程序点的可用表达式,判断是否在各分支都有,不是Verybusy表达式数据流分析的工作。
示例代码:
var x,a,b;
x = input;
a = x-1;
b = x-2;
while(x>0){
print a*b-x; //程序点1: a*b
x = x-1;
}
print a*b; //程序点2: a*b
4.2 数据流方程
p : v = E
OUT ( p) = (IN ( p )
∖
\setminus
∖ {Expr(v)} )
∪
\cup
∪ {E} (transfer function)
IN ( p ) =
∩
\cap
∩ OUT(
p
s
p_s
ps) ,
p
s
p_s
ps
∈
\in
∈ succ( p ) (merging function)
- IN ( p ) : 经过程序点之前非常忙表达式(very busy expressions)集合
- OUT( p ) :经过程序点之后非常忙表达式(very busy expressions)集合
- succ( p ):在CFG中,程序点p的后驱节点
- Expr(v) :含变量v的Verybusy表达式集合
解释:
- 转移函数:
- 首先从等式可以看出,它是从IN 信息推出OUT信息,所以它是一个Forward(前向) 分析。
- 另外,等式中的差运算Expr(v),表示,需要删除(kill)含变量v的非常忙表达式;并运算 {E} , 表示程序点使用的表达式需要加入集合;
- 汇集函数:汇集函数对于后向分析,比较关注多个后继节点IN ( p ) 对该节点OUT( p )的影响,因为Available Expression它是一个Must的信息,所以汇集函数使用交运算。
值得注意的是条件表达式也是表达式,需要加入计算,而活跃性分析与定制到达分析只关心define的表达式,不包含条件表达式计算。
4.3 分析用途
可用表达式和非常忙表达式分析都是关注于表达式是否可化简, 差别在于一个是前向分析,一个是后向分析。
5. 总结
数据流分析,是编译器知识领域比较重要和成熟的领域,在60-70年代就基本完成了问题框架的理论构建,2007图灵奖就颁给了Frances Allen,表彰她在数据流分析的贡献。