整点抽象的
下面默认 \(x\le y\) 意味着 \(x\) 比 \(y\) 更精确,\(y\) 比 \(x\) 更保守。
Galois connection
说的是这么一件事情:
如果给定了
- 程序的语义 \(f_{s}\) 和所有可能的执行状态 \(\Sigma_s=\Set{\sigma_s}\)
- 分析的语义 \(f_{a}\) 和所有可能的分析状态 \(\Sigma_a=\Set{\sigma_{a}}\)
- 指派函数 \(\alpha\colon2^{\Sigma_s}\to\Sigma_a\),将给定的执行状态集抽象成分析状态
- 指派函数 \(\gamma\colon\Sigma_a\to2^{\Sigma_s}\),将给定的分析状态还原为可能的执行状态集
并且有如下性质:
- \(\forall x,x\subseteq\gamma(\alpha(x))\):抽象后再还原可能丢精度,但仍是保守估计(不会低估)
- \(\forall y,\alpha(\gamma(y))\le y\):还原后再抽象不会损失精度
那么 \((\alpha,\gamma)\) 就称为一对 galois connection
需要注意,通常 \(\alpha\) 是构造的,\(\gamma\) 往往是 \(\alpha\) 的伴随产物(这也意味着通常有 \(\alpha\circ\gamma=\text{id}\)),因此上面两个条件也在说:
- 抽象必须保守
- 抽象要尽可能避免丢精度
随机附赠的性质有:
- \(\alpha(\bigcup A)=\bigcup_{a\in A}\alpha(a)\)
- \(\gamma(\bigcap A)=\bigcap_{a\in A}\gamma(a)\)
- \(\alpha(\bot)=\bot\),\(\gamma(\top)=\top\)
Soundness
当我们说一个分析 sound 的时候,说的就是 \(\forall \sigma, \alpha(f_s(\sigma))\le f_a(\alpha(\sigma))\)
Optimality
对于给定的 \(\Sigma_a\),最精确的分析 \(f_a\) 由 \(f_a=\alpha\circ f_s\circ\gamma\) 给出
简单证明一下:
任取单调、sound \(g\colon \Sigma_a\to\Sigma_a\),由 \(g\) sound 立刻有 \(\forall \sigma_a\in\Sigma_a, \alpha(f_s(\sigma_a))\le g(\alpha(\sigma_a))\)
这说明 \(\forall \sigma_s\in\Sigma_s, \alpha(f_s(\gamma(\sigma_s)))\le g(\alpha(\gamma(\sigma_s)))\)。根据定义,\(\alpha(\gamma(\sigma_s))\le \sigma_s\);\(g\) 单调,\(g(\alpha(\gamma(\sigma_s)))\le g(\sigma_s)\)
串起来就是 \(\alpha\circ f_s\circ\gamma\le g\),由 \(g\) 的任意性可知 \(\alpha\circ f_s\circ\gamma\) 最优。
这告诉我们对于给定的格抽象,理论上总是存在一个最优的抽象解释器:粗暴地把抽象状态具体化,交给具体语义的解释器执行,最后把可能的结果收集起来再抽象。
这里有个比较重要的点是这样的:即便所有的原子操作语义都做到了最优抽象,它们的组合也可能达不到最优。
办法:
- 对组合后的原子操作建模,扩大上下文。比如直接对数组(ptr offset deref => array load)和对象(ptr offset deref => load field)操作整体建模
- 做一些语义等价但利于分析的程序变换。比如用易于分析的 IR、做优化