Skip to content

返回学习路线

同一张控制流图:从数据流事实到安全删除 ​

这份任务将八个条目接成三个可核对的终点:在同一菱形图上求事实并实际变换;把一段三轮循环的乘法移到入口;在SSA图里同时传播常量与控制边。它不要求安装编译器框架,Python标准库就能重放,但每一步都要解释为什么允许改动。

入口与交付 ​

先能回答三个问题:may分析为什么在分支用并集?must分析为什么用交集?φ为什么只读取实际进入边的参数?若还不确定,可依次读单调数据流、控制流图与φ节点。

固定语言是单线程、过程内局部标量IR,所有使用有定义,数学整数运算不溢出,加法、乘法、比较、复制都纯粹且总定义。观察包含返回值与分支路径;没有内存、调用、异常、poison、输入输出及资源失败。边界反例会明确换模型,不能混入主证明后还沿用原结论。

交付四项:三个数据流边界表;CSE、复制传播、DCE的逐阶段IR;LICM的循环状态及零次反例;SCCP的边集合、值表与两种迁移。证明说明负责全输入保证,有限运行负责检查具体实现。

运行 Python 3.10 或更高版本:

text
python foundations-dataflow-optimization-checker.py --out result.json
python -O foundations-dataflow-optimization-checker.py --out result-optimized-mode.json

验收不使用会被 -O 移除的 assert。脚本不联网、不调用LLVM、不测机器码性能,也不把fuel耗尽当作发散证明。菱形解释器明确只支持无环输入,意外出现循环会报错;循环示例有独立的自然数计数执行器。

任务一:同一菱形图,三种问题 ​

输入a、b是整数,p是布尔值。静态赋值按块内位置编号:

text
E: E.1 x := a+b
   E.2 y := x
   if p goto T else F
T: T.1 u := a+b
   goto J
F: F.1 u := a+b
   goto J
J: J.1 z := a+b
   J.2 dead := z+1
   J.3 r := u+y
   return r

到达定义问值可能来自哪次写入。J入口的完整集合为 {input:a,input:b,input:p,E.1,E.2,T.1,F.1}。其中u的来源有两项,但每次实际运行只选择其中一项。新增一个不可达的U块,里面定义u并跳J,不得污染这个集合。

活跃性问以后可能还读什么。完整边界如下:

块 IN OUT
E
T
F
J ∅

可用表达式问每条路是否已算过、操作数是否保持。进入E为空,进入T、F、J均为 {add(a,b)};J出口另外包含add(z,1)与add(u,y)。把语句换成a:=a+b时,add(a,b)不能生成,因为出口a已经不同。

三张表的含义不可混用。两个到达定义不自动意味着两个数值不同;表达式可用不自动保证结果载体尚在;值不活跃不自动允许删除有副作用的计算。

任务二:逐阶段真正发出代码 ​

第一阶段CSE用E.1作锚点,将T、F的u赋值和J的z赋值改为读取x。验收证据是:E支配三处,a、b不改写,x也不改写。不要只展示字符串相同。

第二阶段复制传播在J入口同时保留(y,x)、(u,x),执行z:=x后加入(z,x),所以改成dead:=x+1、r:=x+x。复制指令此时仍存在。

第三阶段DCE删除dead、z、两份静态u赋值与y,得到:

text
E: x := a+b
   if p goto T else F
T: goto J
F: goto J
J: r := x+x
   return r

对a=2、b=3、p=false,四个阶段都经过E、F、J并返回10。每次执行的加法次数依次为5、3、3、2;复制次数依次为1、3、3、0。注意源静态赋值有7条,单次路径只执行6条;最终静态与动态赋值都为2条。

检查器枚举a,b∈{-4,…,4}、p∈{false,true},共162个输入,实际运行四份IR并比较结果与路径。这个枚举没有覆盖全部数学整数;对任意a、b都返回2(a+b)的推导,加上每个变换的逐步保持论证,才支持一般结论。

另把a:=3放E,b:=a+1放后继T,T返回0。要求DCE删除轮数中每轮的删除条数为1、1、0:第一轮删除b,重算活跃性后第二轮删除a,最后一轮确认稳定。

任务三:外提必须经受零次循环 ​

使用循环不变代码外提的程序,i与s从0开始,循环执行t:=ab、s:=s+t、i:=i+1,条件i<n。用新鲜h在preheader算ab,循环内保持t:=h的位置。

取a=2、b=3、n=3,两版循环后的状态都为(1,6)、(2,12)、(3,18),返回18;乘法3次对1次。n=0时都返回0,但乘法0次对1次,说明代码移动不保证每个输入都减少工作。脚本检查a,b∈{-3,…,3}、n∈{0,…,5},共294组。

迁移:每轮末尾再写a:=a+1,n=3时源结果27,错误外提结果18。另一模型把运算换成有除零trap的1/d,n=0,d=0时源返回0,错误外提trap。前者违反值不变,后者违反提前求值安全;它们是两项不同的许可。

任务四:让常量与边一起稳定 ​

SCCP的独立SSA输入如下:E定义a=4、b=6、c=(a==4),真边去T计算t=a+b,假边去F取f=q;J定义v=phi(T:t,F:f)、w=v*2、d=(w==20),真边R返回w,假边B返回99。

最终已激活边恰为START→E、E→T、T→J、J→R;a=4、b=6、c=true、t=10、v=10、w=20、d=true。输入q为⊤,F中未执行的f仍为⊥。φ只合流已激活入边,不能让F的未知输入污染v。

将E的条件改成未知布尔输入p,再将F改成常量14:两条进入J的边都激活,v、w、d均为⊤,J的两条出口都保留。若F改成10,v仍为10,w=20,d=true,B再次被排除。结果JSON含三种情况的事件轨迹。

本检查器实现分析,没有自动输出清理后的SSA。主例可手工化简为return20,但“分析可证明”与“代码已重写并通过结构验证”是不同交付。SCCP可处理循环的数据流条件,不等于能够判定任意源程序是否终止。

最后四个拒绝理由 ​

  1. 把y:=x后的所有y永久改成x:如果x后来改变,就读错旧值
  2. 因x死了而删除x:=1/0:在trap语义下改变观察
  3. 两分支各算一次表达式,就在汇合读取仅一支定义的u:另一支没有这个载体
  4. 队列尚未清空就删除未激活边:后续信息仍可能激活它

验收应能给每项写出具体输入和错误输出,而不只是说“注意边界”。报告里的运行值、变换树和数据流集合均由所附脚本产生;对真实机器整数、别名内存、异常、并发或LLVM语义的保证仍需新模型与新证明。