说说静态单赋值(SSA,Static Single-Assignment)
精确的数据流分析是让编译优化能高效进行的基础。 SSA就是一种高效的数据流分析技术,目前几乎所有的现代编译器,如GCC、Open64、LLVM都有将SSA技术的支持, 不仅仅是编译器,Jikes RVM, HotSpot JVM, .Net的Mono,Python的Pypy, Andoroid的Dalvik,这些虚拟机/解释器中的Just-in-Time Compiler也有了SSA的支持。 Firefox的下一代JavaScript引擎IonMonkey中,也将为其JIT引入SSA。
可以看到,几乎所有热门的语言所用的热门编译器/解释器/虚拟机中都有了SSA。
Table of Contents
1 SSA是什么?
SSA即静态单赋值,Static Single-Assignment,这是一种中间表示形式。 之所以称之为单赋值,是因为每个名字在SSA中仅被赋值一次.
如下图中的一段程序的控制流图。从这张图中可以看到,最后一个基本块中y值的定义或者来自左侧的分支,或者来自右侧的分支。
将每个赋值语句中的变量赋予一个唯一的名称后,一般新名称采用原变量+版本号(Version)的形式。 对于上面这段控制流图,就变成如下形式:
这张图中有个问题,有分支时,若分支中有对变量的操作,就无法确定使用了哪个版本的变量。 因此,引入了PHI节点。如下图所示:
PHI将分支中的y1和y2连接,并生成一个新的定义y3。有了PHI节点后,最后一个基本块中y3的定义来自之前的PHI节点,PHI节点中的两个操作数y1和y2分别来自左右两个分支。
2 SSA的作用
在SSA中间表示中,可以保证每个被使用的变量都有唯一的定义,即SSA能带来精确的使用–定义关系。 而在图SSA_example1.1中的y值定义却非常模糊。
概括起来,SSA带来四大益处:
- 因为SSA使得每个变量都有唯一的定义,因此数据流分析和优化算法可以更加简单
- 使用-定义关系链所消耗空间从指数增长降低为线性增长。若一个变量有N个使用和M个定义,若不采用SSA,则存在M×N个使用-定义关系。
- SSA中因为使用和定义的关系更加的精确,能简化构建干扰图的算法
- 源程序中对同一个变量的不相关的若干次使用,在SSA形式中会转变成对不同变量的使用,因此能消除很多不必要的依赖关系。
有了精确的对象使用–定义关系,许多利用使用–定义关系的优化就能更精确、更彻底、更高效。如
- 常数传播
- 死代码删除
- 全局
- 部分冗余删除
- 强度削弱
- 寄存器分配
2.1 SSA与寄存器分配
因为SSA使得依赖分析更加简单、精确,而且PHI节点中的变量不可能同时活跃。因此在SSA形式能协助完成寄存器分配。 实际上,GCC最早的SSA就是GCC 3中RTL阶段。
3 SSA的转换
讲了这么多有关SSA的优点,接下来介绍一下一般编译器构建SSA的方式。
3.1 从普通中间表示到SSA
两步走战略:
- 插入PHI节点: PHI节点要插在控制流图的汇聚点处(joint point), 只要在汇聚点之前的分支中有针对某个变量的修改, 就需要在该汇聚点插入针对该变量的PHI节点。 PHI节点的操作数是分支路径中重新定义的变量。
- 变量重命名: 在插入PHI节点后,SSA中所有针对变量的定义就具备了,接下来就依次在定义处重命名变量,并替换对应的变量使用处。
此外,为了节省内存空间,简化SSA上的算法,我们需要将插入的PHI节点数目最小化。 因为PHI节点本身只是一个概念性的节点,若插入过多不必要的PHI节点,算法就需要在控制流图的汇聚点针对每个分支做分析。 可以借用变量的支配边界(dominance frontier)进行PHI节点数目最消化。一般都通过直接计算支配边界的方式插入PHI节点。
4 更复杂情况下的SSA
4.1 数组、指针等别名
上面关于SSA的讨论基本都是针对单个简单变量的SSA操作,那么对于复杂的指针、数组之类的访存,SSA应该如何处理呢? 数组和指针使得编译器无法确定define和use的具体变量。
参考资料7给出了一种定义方式,通过引入maydef,mayuse和zero version使得编译器也能对别名(即指针和数组)存在的程序做SSA分析。 若通过指针为其所指区域赋值,就在此处插入maydef,表示可能对变量做了定义。同理,对使用指针所指向区域的值的,就插入一个mayuse。 因为无法确定指针所指向的到底是哪个变量,为了正确性,需要对所有变量都插入maydef动作。同样mayuse也是针对所有变量的。
当指针操作较多时,这种方式就会引入过多的新变量版本。因此就增加了zero version。 zero version的作用就是尽量把maydef所带来的版本数降低。 将那些很可能不会别名的都使用相同的zero version。 比如某个变量通过maydef产生了一个新版本之后,若还会有新的maydef操作,则直接生成zero version,不再生成新的version。
4.2 堆上的存储
在堆上分配的存储空间,一般编译器都将整个堆看作一个对象,来做SSA。
4.3 复合结构–结构体
因为结构体也是由很多元素构成的,所以就存在两种处理方式:把结构体整个看作一个整体做SSA、把结构体的每个元素看作一个对象做SSA。 后者相比前者,因为分的更细,在结构体操作频繁的程序中能带来不错的优化效果。
5 GCC中的SSA
GCC的SSA
- tree-ssa.c
- tree-into-ssa.c:将函数转换为SSA形式,插入PHI节点,对于未初始化的变量给出警告。
- tree-ssa-dce.c:扫描整个函数,标记无副作用且结果并未被使用的语句,所有存储操作都视为有副作用。
- tree-ssa-dom.c:支配关系相关优化:复写传播、常数传播、表达式简化、冗余消除、Jump Threading?
- tree-ssa-forwprop.c:前向传播单一引用变量,通过将仅使用一次的变量用相应的表达式替代来尝试容易删除。
- tree-ssa-copyrename.c:尝试将由拷贝动作产生的SSA变量用原变量替换之,优化符号表。
- tree-ssa-phiopt.c: 识别表达条件表达式的phi节点,并将其重写为直线代码。
- tree-ssa-alias.c:流敏感基于SSA的指向分析,得到可能-别名,一定-别名和逃逸分析信息。 这些信息将用于将变量从内存中地址可用对象提升为非别名变量,这样这些变量就能使用SSA形式的分析和优化了。
- tree-ssa-structalias.c:用于过程间的指向分析。
- tree-sra.c:将合适的无别名局部复合变量转换为一个标量集合,并进而转换为SSA形式。
- tree-ssa-dse.c:删除那些无用的存储操作
- tree-ssa-sink.c:将存储和赋值语句尽量下沉到和它们的使用点接近的位置。
- tree-ssa-pre.c:部分冗余删除、load语句移动、完全冗余删除
- tree-ssa-loop.c: SSA形式的循环优化
- tree-ssa-loop-im.c:循环无关语句移动
- tree-ssa-loop-ivcanon.c:循环标准化
- tree-ssa-loop-ivopts.c:索引变量优化
- tree-ssa-loop-unswitch.c:将循环无关的条件跳转移到循环外
- tree-vectorizer.c, tree-vect-analyze.c, tree-vect-transform.c:自动向量化
- tree-ssa-ccp.c:条件常数传播
- tree-ssa-copy.c:条件复写传播
- tree-vrp.c:取值范围传播
- tree-outof-ssa.c:从SSA形式转换回普通形式
6 open64 中的SSA
open64中的SSA主要用于循环嵌套优化、过程间优化以及普通的函数内优化。 除了循环变换和内联优化外的所有机器无关优化都基于SSA做。 这部分可以说是Open64的重要卖点,对应的代码在osprey/be/opt下。
Open64在没有过程间优化时,主要以函数为单位进行,基于控制流图和别名分析得到的信息构建SSA。
- opt_goto.cxx:goto语句转换,方便做SSA
- opt_loop.cxx:循环正规化
- opt_sym.cxx:构建相关符号表
- opt_alias_class.cxx:别名分类,方便别名分析
- opt_cfg.cxx:构建控制流图,包括支配树,不可到达代码识别,if语句转换
- opt_tail.cxx:尾递归消除
- opt_alias_analysis.cxx:流无关别名分析
- opt_ssa.cxx:构建基于WHIRL的SSA
- opt_dse.cxx:死store删除
- opt_htable.cxx:构建HSSA–基于哈希的全局值编号SSA
- opt_ivr.cxx:索引变量标准化
- opt_prop.cxx:复写传播
- opt_revise_ssa.cxx:将非直接变量展开成直接变量
- opt_dce.cxx:死代码删除
- opt_cfg_trans.cxx:控制流转换
- opt_rename.cxx:SSA变量重命名、更新
- opt_du.cxx:构建define-use信息
- opt_etable.cxx:基于表达式的部分冗余删除
- opt_estr.cxx:强度削弱
- opt_ehoist.cxx:代码提升
- opt_lftr2.cxx:线性代码测试、替换
- opt_vn.cxx:基于值编号的完全冗余删除
- opt_ltable.cxx:针对load的部分冗余删除
- opt_stable.cxx:store Partial Redundancy Elimination 针对store的部分冗余删除
- opt_bdce.cxx: Bitwise dead code elimination–针对结构体
- opt_htable_emit.cxx: 从SSA转换回WHIRL中间表示
7 相关资料和文献
- http://en.wikipedia.org/wiki/Static_single_assignment_form
- http://en.wikipedia.org/wiki/SpiderMonkey_%28JavaScript_engine%29
- Advanced Compiler Design and Implementation, by Steven S. Muchnick, Page 252-265
- Modern Compiler Implementation in C, by Andrew W. Appel, Page 433-473
- Crafting A Compiler, by Charles N. Fischer, etc , Page 410-414
- 8. Static Single Assignment Form, by Marcus Denker, http://marcusdenker.de/talks/08CC/08IntroSSA.pdf
- Effective Representation of Aliases and Indirect Memory Operations in SSA Form, Fred Chow, Sun Chan, etc. Compiler Construction 1996
您可能也喜欢: |
Open64 课程–全局标量优化(WOPT II) |
Open64 课程–全局标量优化(WOPT I) part II |
open64课程–过程间分析优化(IPA) |
Open64 课程–全局标量优化(WOPT I) part 1 |
Open64课程–反馈指导优化(FDO) |
无觅 |