议题:编译技术驱动静态分析革新
本议题探讨编译技术驱动的静态代码分析革新。聚焦于两方面:一是 SSA(静态单赋值形式)如何通过精确的 Use-Def 链和 Phi 指令,为静态分析提供更坚实的理论基础,并应对闭包带来的跨过程分析挑战;二是探讨如何将代码表示持久化到数据库中,利用 DSL(领域特定语言)实现高效查询和分析,从而构建基于数据库的新分析范式。议题旨在强调,静态代码分析的进步离不开底层编译技术的支撑,并将 理论创新与实际应用紧密结合。
高级编译技术与静态分析
编译技术重要里程碑
在 ssa.to 的理论章节中,我们在概论中简单介绍了编译技术以及 SSA 的诞生历程:
在20世纪80年代到90年代初期,编译器优化技术经历了一次革命性的发展。这个时期出现了一系列重要的优化技术,包括数据流分析(如到达定义分析、活跃变量分析)、经典优化技术(如常量折叠、死代码消除、公共子表达式消除)、基于控制流图的优化,以及过程间分析等。但最具革命性的突破是1991年静态单赋值形式(SSA Form)的引入。SSA形式的核心思想是:程序中的每个变量只能被赋值一次。这个看似简单的约束却带来了深远的影响。
SSA 基本概念
SSA 的基本性质:
-
单一赋值规则:
-
φ 函数定义:
其中:
- 是新的变量版本
- 是来自 k 个前驱基本块的变量版本
- 支配性质:
在 SSA 中,支配(dominance)是指控制流图(CFG)中基本块之间的关系,而不是数据依赖关系。
这就是 SSA 形式最核心的定义,包含了单一赋值、φ 函数和支配关系三个关键要素。
SSA 编译器的编译过程
SSA 引发数据流关系革新
根据定义我们知道,SSA 主要描述的其实是数据流关系,我们在 SSA 中描述数据流关系一般使用 Use-Def 链来表达。
在不抽象出 SSA 之前,我们对数据流的描述非常模糊,考虑如下代码,不使用 SSA 我们几乎无法分析变量究竟是怎么回事儿:
x = 1;
y = 2;
if c1 {
x = 3;
if c2 {
y = 4;
} else {
y = 5;
}
} else {
x = 6;
}
e = x + y;
println(e);
没有 SSA 的 CFG + 数据流如下:
在 SSA 化后,我们得到代码如下:
x1 = 1;
y1 = 2;
if c1 {
x2 = 3;
if c2 {
y2 = 4;
} else {
y3 = 5;
}
y4 = φ(y2, y3)
} else {
x3 = 6;
}
x4 = φ(x2, x3)
y5 = φ(y1, y4)
e1 = x4 + y5
println(e1)
他的数据流图如下:
抽象出 Use-Def 链,如下:
我们通过 SSA 构建上述的 Use-Def 关系,可以直接发现如下现象:
- e1 直接依赖于 x4 和 y5
- 所有变量的定义点都是唯一的
- φ 函数清晰地表示了变量的多个可能来源
-
变量追踪:可以精确追踪任何变量的定义来源,并且可以清晰地分析变量在不同控制流路径上的值
-
优化机会:容易识别常量传播的机会,并且几乎可以直接消除死代码,可以准确判断变量的活跃范围,
-
分析效率:直接的前向/后向分析,无需迭代计算到达定值,版本号简化别名分析
SSA 解决难点:闭包问题
什么是闭包
闭包(Closure)是一个函数与其相关的引用环境的组合体。从技术角度来说,闭包是一个记录结构(record),它包含了一个函数和一个关联的环境组件。
这个环境组件中包含了函数体内引用的、但是既不是函数参数也不是函数局部变量的所有变量的绑定。