“因此本分析不等于SCCP。SCCP 在一个 SSA 定义上合流不同路径的可能值,通常会把2和5合成“非常量”;本页的 S 标签只要求各版本能带着各自的具体 k,二者回答的问题不同。”
形式陈述
两份状态要分开存
输入是良构SSA:每个名字一个静态定义,普通使用受定义支配,φ 参数按对应入边读取。运算纯粹、确定、总定义;输入参数可以取任意声明类型的值。整数按数学整数,没有未初始化、poison、内存或异常;这些不是从 LLVM 自动继承的默认规则。
每个 SSA 名字保存以下抽象值:
:目前尚未从已激活路径得到值- 常量 c:目前所有已获证据一致为 c
:不再能用一个常量概括,可能为不同值
次序为
另一份状态是控制边集合 X,初始只有人工 START→E。边只增加、不删除。称它们“已激活的可能执行边”更准确:一条边被加入,不证明一定有真实输入走它;算法保守地承认这种可能。
两种消息驱动同一个求解器
维护“新控制边”队列和“值发生变化”队列,也可以像下载实现那样用一个带类型标签的队列。预先建立每个 SSA 定义的使用者列表。
收到尚未加入 X 的 P→B 时,加入它并重新计算 B 顶部的全部 φ。若 B 第一次激活,还访问它的普通指令与末端跳转。B已经激活时,新增入边主要改变φ,不能因为“这个块访问过”就跳过。
普通运算若全部输入为常量,按源语义算出常量;若有 0*x=0 等额外恒等式。新结果再与原保存值做 join,保证只沿偏序上升,不能把已知多值的
φ 的规则是
无激活入边时合流为
对于循环,第一次可能只知道一个φ入口值;后来回边激活或回边值从常量变多值,会重新通知φ。边集合不会撤回,值也不会从
直觉
普通常量传播看到一条路传来10、另一条路传来未知输入,就只能说汇合结果未知。但如果第二条路根本走不到呢?SCCP 一边找常量,一边确定哪些控制边需要考虑:常量能排除分支,排除分支又能使更多值成为常量。这两个过程必须一起求稳定结果。
例子与边界
一个完整的常量链
E: a := 4
b := 6
c := (a == 4)
if c goto T else F
T: t := a+b
goto J
F: f := q // q是任意整数输入
goto J
J: v := phi(T:t,F:f)
w := v*2
d := (w == 20)
if d goto R else B
R: return w
B: return 99
激活 E 后,a=4、b=6、c=true,于是只激活 E→T。T 给出 t=10,再激活 T→J。J 的 φ 只看已激活的 T→J 槽,所以 v=10;随后 w=20、d=true,只激活 J→R。最终可达块集合为 {E,T,J,R},q虽为
若忽略条件、把两条边预先都激活,f便为
三项边界防止过度承诺
把 E 的条件改成布尔输入p。若 T给10、F给14,则两边激活,v和w都是
再考虑两条分支分别产生 (x,y)=(1,1) 和 (2,2),汇合后计算 z=x−y。逐变量常量域把x、y都视为
这里的
推论与应用
为什么稳定后的删除可信
中间时刻的未激活边还可能在以后被发现,因此不能提前把它删掉;同样,中间的常量以后可能升到
对任一真实有限执行前缀按步数归纳。入口边已激活,输入参数由
这也排除了“真实走到条件却永久为
本页检查器完整实现该分析,输出边、值和事件列表;它没有自动发出清理后的SSA。主例可手工读成直接返回20,是独立的终点答案,不把分析结果文件误称为编译器已完成全部清理。
复杂度必须说明φ的实现
设 N 个指令与块、M 条控制边、U 次定义—使用出现。每个值最多发生两次严格上升,每条控制边最多激活一次;固定元数普通运算的工作因此与这些事件数线性相关。
但若每次有φ消息都重新扫描它的 k 个槽,该φ最多收到 O(k) 次新边或输入变化消息,代价可能为 O(k²)。下载检查器就是这种简单实现,故总界为
终点请先不运行脚本,写出主例从START→E到J→R的全部激活边,列出f为何仍为
参考资料
- Mark N. Wegman and F. Kenneth Zadeck, “Constant Propagation with Conditional Branches,” ACM TOPLAS 13(2), 1991, pp. 181–210,论文全文,§3.4 的稀疏条件算法、§4 的可靠性、§5.4 的表达式求值成本。原文使用与本文相反的格绘制方向;本页始终明确以信息覆盖增大的
口径叙述。 - Brown 技术报告 CS-91-22核对作者、题名与1991版本。本文SSA、具体输入和简单φ重扫实现均单独规定。