什么是静态单赋值 SSA

SSA 是 static single assignment 的缩写,也就是静态单赋值形式。顾名思义,就是每个变量只有唯一的赋值。

以下图为例,左图是原始代码,里面有分支, y 变量在不同路径中有不同赋值,最后打印 y 的值。右图是等价的 SSA 形式,y 变量在两个分支中被改写为 y1, y2,在控制流交汇处插入 Ф 函数,合并了来自不同边的 y1, y2 值, 赋给 y3, 最后打印的是 y3。

图1 原始代码与 SSA 形式及相应 CFG 控制流图

总结 SSA 形式的两个特征就是:

  1. 旧变量按照活动范围(从变量的一次定义到使用)分割,被重新命名为添加数字编号后缀的新变量,每个变量只定义一次。
  2. 控制流交汇处有 Ф 函数将来自不同路径的值合并。 Ф 函数表示一个 parallel 操作,根据运行的路径选择一个赋值。如果有多个 Ф 函数,它们是并发执行的。

这里引入两个名词 use-def chain 和 def-use chain。use-def chain 是一个数据结构,包含一个 def 变量,以及它的全部 use 的集合。相对的,def-use chain 包含一个 use 变量,以及它的全部 def 的集合。以图2左图为例,虚线就是 x 每处定义的 def-use chain. 传统代码因为变量不止一次定义,所以每个定义的 def-use chain 非常复杂。再看右图,SSA 形式下没有同名变量,每个变量只定义一次,所以同名的 use 都是属于它的 def-use chain.  而且因为每个变量 use 前都只有一次 def, 所以 use-def chain 是一对一的。可见,SSA 形式下的 def-use chain 与 use-def chain 都得到了简化。

图2 两种形式下的 Def-use chain

SSA 形式的优点不仅在于简化 def-use chain 与 use-def chain,它提供了一种稀疏表示的数据结构,极大方便了数据流分析。如图3 所示,左边是传统的基于方程组的数据流分析,右边是基于 SSA 形式的数据流分析。前文讲过传统数据流分析是在基本块上沿控制流路径或逆向迭代传播,SSA 形式的 def-use chain 与 use-def chain 直接给出了更多信息,数据流值传播不局限于控制流路径。可见 SSA 形式可以简化数据流分析。

图3 迭代数据流分析与基于 SSA 形式的数据流分析

SSA 的几种类型

SSA 有几种不同风格

最小 SSA

最小静态单赋值形式 (minimal SSA) 有以下特点:同一原始名字的两个不同定义的路径汇合处都插入一个 Ф 函数。这样得到符合两大特征的且拥有最少 Ф 函数数量的 SSA 形式。但是这里的最小不包含优化效果,比如死代码消除,或者值有可能经 live-range 分析是死(参考上篇数据流分析内容)的。

剪枝 SSA

如果变量在基本块的入口处不是活跃 (live) 的,就不必插入 Ф 函数。一种方法是在插入 Ф 函数的时候计算活跃变量分析。另一种剪枝方式是在最小 SSA 上做死代码消除,删掉多余的 Ф 函数。

半剪枝 SSA

鉴于剪枝 SSA 的成本,可以稍微折衷一点。插入 Ф 函数前先去掉非跨越基本块的变量名。这样既减少了名字空间也没有计算活跃变量集的开销。

严格 SSA

首先引入一个名词叫支配。如果从程序入口到一个结点 A 的所有路径,都先经过结点 B,则称 A 被 B 支配。如果 A 不等于 B,则称 A 被 B 严格支配,A 的支配结点集记为 Dom(A),Dom(A) 中与 A 最接近的结点称为直接支配结点,记为 IDom(A)。如下图所示,B0 支配 B1,B1 支配 B2, B5... 支配关系是可传递的,例如 B0 也支配 B2, B5... 这样可以根据支配关系构造一棵树,路径上的父结点直接支配子结点,根结点支配所有后代结点。

图4 支配树

如果一个 SSA 具有以下特点:每个 use 被其 def 支配,那么称为严格 SSA。如下图所示,左图 a, b 的 def 没有支配 use,所以不是严格 SSA。在右图中,汇合点插入2个 Ф 函数,重新编号了变量,保证了支配性。其中 ⊥ 表示未定义。

图5 严格 SSA

传统与变形 SSA

构造 Ф-web 并查集,将 def-use chains 有相同变量的都连接起来得到一个网络,比如 Ф 函数两边的 define 或 parameter 变量。可以得到若干网络。传统 SSA 的特点是 Ф-web 中变量的 live-range 互相不干涉。传统 SSA 经过优化例如复制传播之后,可能会被破坏这个性质,就称为变形 SSA。

SSA 构造算法

这里介绍一种基于支配边界的 SSA 构造算法。这个算法分为两个步骤:1,在支配边界插入 Ф 结点;2,变量重命名。

计算支配边界的目的是只在需要的地方插入 Ф 结点,支配边界的定义是:A 支配 B 的一个前驱但不严格支配 B,则称 B 为 A 的支配边界。A 的所有支配边界组成的集合记为 DF(A)。DF 即 dominace frontier。

图6 计算 DF 图

计算支配边界的方法如图6. 左图是控制流图。首先构造出支配树。中图是一棵支配树,从父结点到子结点的边是支配边。控制流路径除了支配边,就是汇合边,例如 D 到 E,F 到 G。首先汇合边的目的结点就是起点的支配边界。例如 E 是 D 的支配边界,F 是 G 的支配边界。然后将汇合边的起点向其直接支配点移动,例如 D 移动到 C,若 C 不是 E 的支配结点,则 E 也是 C 的支配边界,以此类推。最右图则表示最终计算结果,每个箭头指向结点是起点的支配边界。算法伪码如下:

```

for node in all nodes of CFG

if n has multiple predecessors

for each predecessor p of n

runner = p

while runner != IDom(n)

add n to DF(runner)

runner = IDom(runner)

```

计算完成支配边界后基本块 b 中对 x 定义,则在 DF(b) 内每个结点起始处放置一个 Ф 函数。放置过 Ф 函数的基本块其 DF 也要继续放置 Ф 函数。

最后是变量重命名。

每个全局名(对应半剪枝类型,即不考虑不跨越基本块的变量)变成一个基本名,对其各个定义添加数字编号。例如 x,对第一个定义命名为 x1,第二个定义命名为 x2... 方法是在支配树上先序遍历,在每个块上首先重命名 Ф 函数的定义,然后依次访问各条指令,用当前 SSA 名重写各操作数,并为操作的结果创建一个新的 SSA 名。然后使用当前 SSA 名改写后继块 Ф 函数的参数。最后对支配树的子结点递归处理。返回后将 SSA 名恢复前一个状态。所以这里可以用每变量一个栈来存放 SSA 名,压栈时 SSA 名的编号递增,处理完一个基本块后本块内生成名全部出栈。 伪代码如下:

```

NewName(n)

i = counter[n]

counter[n] ++

push i onto stack[n]

return "ni"

Rename(b)

for each Ф-function "x = Ф(...)", rewrite x as NewName(x)

for each operation "x = y op z", rewrite y as top(stack[y]), rewrite z as top(stack[z]), rewrite x as NewName(x)

for each successor of b in CFG, fill in Ф-funcitons

for each successor s of b in dominator tree, Rename(s)

popstack[x]

```

SSA 解构算法

处理器不能处理 Ф 函数,所以我们需要将 SSA 形式转换回可执行代码,这就是 SSA 解构。

SSA 解构是去掉 Ф 函数的操作,但是需要增加一些复制。例如  xi = Ф(xj, xk),去掉这一条语句后应该沿着传入 xj 的边插入 xi = xj,沿着传入 xk 的边插入 xi = xk,就能保证执行正确。但这里面有几种特殊情况:

关键边 critical edge

边的源结点有多个后继,目标结点有多个前驱,则称为关键边。这种情况需要拆分关键边,在关键边里面增加一个基本块,并将复制操作放在这个基本块里。以下图为例。图 (a) 到 图 (b) 是构造 SSA 后进行复制折叠的优化。图 (c) 是插入复制不正确的例子。i1 的 Ф 删除后,在前继块插入复制。但是另一条后继路径上给 z0 赋值时 i1 的值却不正确。如果按照图 (d) 所示,拆分关键边后复制就是正确的。

图7 关键边拆分

丢失复制问题 lost-copy

仍然以图7 为例,如果无法拆分关键边。那么就出现了图 (c) 的复制丢失问题。有一种解法是分析复制操作的目标,如果是活动状态,则建立一个临时变量,重写后续引用。例如图 (e) 所示。

交换问题 swap

在语义上要求同一基本块内的 Ф 函数并发执行。图 (c) 是解构 SSA 后插入复制操作的结果。因为操作不是并发而是顺序执行的,x1, y1 的交换失败了。这个问题的解法是分析 Ф 函数有没有引用同一基本块其他 Ф 函数的结果,对于引用形成的环则必须插入临时变量。

图8 交换问题

这些特殊 case 其实并不容易遇到,往往是复制折叠优化等代码被重排和改变的操作之后才会出现。如果是新建的 SSA,则不会有这种问题。

以上是对 SSA 算法的简单总结。例子与图引用自《static single assignment book》与 《engineering a compiler》。

静态分析之数据流分析与 SSA 入门 (二)相关推荐

  1. SQL基础使用入门(二): DML语句和DCL语句

    SQL语句第二个类别--DML 语句 DML是数据操作语言的缩写,主要用来对数据表中数据记录实例对象进行操作,包括插入.删除.查找以及修改四大操作,这也是开发人员使用中最为频繁的操作. 1.插入记录 ...

  2. 文本分类入门(二)文本分类的方法

    文本分类入门(二)文本分类的方法 文本分类问题与其它分类问题没有本质上的区别,其方法可以归结为根据待分类数据的某些特征来进行匹配,当然完全的匹配是不太可能的,因此必须(根据某种评价标准)选择最优的匹配 ...

  3. 转 Python爬虫入门二之爬虫基础了解

    静觅 » Python爬虫入门二之爬虫基础了解 2.浏览网页的过程 在用户浏览网页的过程中,我们可能会看到许多好看的图片,比如 http://image.baidu.com/ ,我们会看到几张的图片以 ...

  4. java类作用域标识符_java入门 (二) 标识符、数据类型、类型转换、变量、常量、作用域...

    java入门(二) 标识符 数据类型 类型转换 变量.常量.作用域 本次笔记引用B站:狂神说,虽然早就会了,现在回头来敲下基础,加深印象 1.标识符: java所有的组成部分都需要名字.类名丶变量名丶 ...

  5. MySQL入门 (二) : SELECT 基础查询

    1 查询资料前的基本概念 1.1 表格.纪录与栏位 表格是资料库储存资料的基本元件,它是由一些栏位组合而成的,储存在表格中的每一笔纪录就拥有这些栏位的资料. 以储存城市资料的表格「city」来说,设计 ...

  6. 微信小程序入门二:底部导航tabBar

    小程序底部导航栏组件tabBar,可以参考下官方的API:tabBar 先看代码 //app.json {"pages":["pages/index/index" ...

  7. conan入门(二):conan 服务配置-密码管理及策略

    conan 服务配置 密码管理及策略配置 第一次以管理员身份(admin)使用默认密码(password)WEB登录入JFrog Artifactory后台时,系统就提示要求我修改密码,因为现有密码太 ...

  8. CSS入门二、美化页面元素

    零.文章目录 CSS入门二.美化页面元素 1.字体属性 CSS Fonts (字体)属性用于定义字体系列.大小.粗细.和文字样式(如斜体) (1)字体系列font-family font-family ...

  9. Pascal游戏开发入门(二):渲染图片

    Pascal游戏开发入门(二):渲染图片 渲染静态图片 新增一个Texture,然后Render出来 创建Texture,并获取尺寸 procedure TGame.Init(title: strin ...

  10. OpenGL入门二——变换

    OpenGL入门二--变换 一.预备知识 二.实现 三.控制 四.预览与源码 一.预备知识 齐次坐标:https://blog.csdn.net/zhanxi1992/article/details/ ...

最新文章

  1. python如何开发小软件-Python程序员,如何快速开发一个小程序
  2. ROS Gazebo(一):安装与使用
  3. 机器人整机主要产品规格参数
  4. python文字识别算法_Python图像处理之图片文字识别(OCR)
  5. ArcObject开发,“异常在 ESRI.ArcGIS.Version.dll”错误
  6. 升级 ubuntu_重要:Canonical 安全更新,请相关 Ubuntu 版本赶紧升级
  7. python模拟ajax请求_短信炸弹—用Python模拟ajax请求
  8. Leetcode--304. 二维区域和检索
  9. c语言程序设计 猜数字,C语言程序设计(猜数字游戏)报告.doc
  10. Leetcode每日一题:112.path-sum(路经总和)
  11. 漫游飞行_涨知识了,手机的飞行模式还能这么用?
  12. 算法界的“视界杯”,2021腾讯广告算法大赛来了!
  13. HDLCoder的系统设计
  14. 转载(吃货告诉你,PAAS、IAAS和SAAS之间的区别)
  15. ISA服务器安装设置全集
  16. [#32;] 在wordpress [the_excerpt()] 函数执行的妙用
  17. HDU 6146:Pokémon GO
  18. dfs python
  19. android横向卡片式布局,创建卡片式布局  |  Android 开发者  |  Android Developers
  20. Node.js Async Await in ES7

热门文章

  1. Linux怎么样修改文件编码,Linux下批量修改文件编码
  2. 固态硬盘是什么接口_ssd固态硬盘是什么意思,有什么好处,固态硬盘和机械硬盘的区别...
  3. 安卓的网络视频播放器(简易版)
  4. freeswitch mrcp 源码分析--数据接收(上)
  5. 华为防火墙USG6309E开局基础配置之安全策略
  6. 海外网红营销是战略还是战术?从“PDCA循环”层面规划营销
  7. java 苹果cms 萌果_苹果cms打包app
  8. 用计算机进行绘画教案,第二课 用鼠标键盘创作作品--电脑绘画教学设计(教案)...
  9. 视频带宽(码流_分辨率_帧率)
  10. x264去方块滤波函数解析(二)