“指令选择把较抽象 IR 的运算、条件与访存转换成目标机器或机器无关低层 IR 的指令序列。经验证的选择器不仅计算 $Q=\operatorname{select}(P)$,还证明这个编译阶段…”
形式陈述 ​
设编译阶段
这正是精化关系的“目标不新增源未允许观察”口径。源语义若非确定,目标可以选择其中一种合法行为,未必保持集合相等;若源程序安全且语义确定,并另证不丢失终止或发散行为,才可能加强为相同观察。
命题还要规定未定义行为。若源一旦 UB 就允许任意后续行为,可把 UB 状态解释为行为全集的顶部,编译器在该点之后无需提供通常保证;trap 若是确定、可观察的终止事件,则不能与 UB 混同。外部调用、I/O 顺序、终止值、发散与资源耗尽是否进入观察,都属于定理参数而不是自然语言省略项。
环境也须满足共同假设。例如源、目标调用同名外部函数时,参数编码与返回关系要一致,环境不得在两层观察本应隐藏的内部临时状态。对开放程序,定理通常量化所有满足接口约束的环境;只固定测试时使用的一套库,会把链接假设误装成编译器本身的正确性。
直觉
语义保持不是要求源、目标逐条指令相同,而是要求外部观察者无法看到契约禁止的新结果。优化可以删除临时量、合并基本块或把一条指令扩成数步;状态形状和步数因此不同。证明需要一座桥,把两边状态及若干步对应起来,最终比较完整行为。
“保持”也不总是双向相等。源语言可能允许表达式求值顺序的多种结果,编译器固定其中一种,这是减少非确定性,仍满足目标行为包含于源行为。反过来,若目标新增一个崩溃结果,即使它还保留所有正常结果,也破坏上述精化方向。
例子与边界
设源 IR 的常量计算分两步:
常量折叠目标直接含
边界例取
回归测试只检查有限输入和有限运行。即使一百万次测试都相等,也不能推出所有状态、路径、发散执行与环境交互上的集合包含。测试适合发现实现错误,语义定理则量化全部合法程序和执行;两者作用互补,不能互相改名。
源程序已触发 UB 与“编译器提前制造 UB”也要区分。若某输入的源执行在第十步才越界,目标不能在第一步、尚未匹配前九步可见事件时任意行动。行为顶部只从已到达 UB 的相关状态开始放宽;在此之前,模拟仍要保存事件前缀与状态不变量。
推论与应用
若每个阶段
组合要求相邻 IR 对事件、返回和错误的编码一致;一层把 trap 当事件、下一层把它当 UB,会让形式上可复合的符号失去实际含义。
常用证明方法包括编译器前向模拟、编译器后向模拟、逻辑关系和翻译验证。它们是建立语义保持的不同证据接口,并不等于性质本身。选择方法应由确定性、非确定性、内部停顿与阶段实现的可信边界决定。
参考资料
- Xavier Leroy, “Formal Verification of a Realistic Compiler,” Communications of the ACM 52(7), 2009, pp. 107–115.
- Xavier Leroy, “A Formally Verified Compiler Back-end,” Journal of Automated Reasoning 43(4), 2009, pp. 363–446.
- John C. Reynolds, Theories of Programming Languages, Cambridge University Press, 1998, Chapters 6–8.