“一种小可信基架构让外部 allocator 产生位置映射与重写程序,再以翻译验证检查:所有干涉边颜色不同、预着色与类别正确、spill 槽分离、每条重写指令满足局部状态关系。只要 check…”
形式陈述
翻译验证把编译器或优化器视作可能不受信任的产生器。给定源程序
关键定理不是“编译器总正确”,而是验证器的可靠性:
右侧采用语义保持阶段的行为精化契约:目标的每种观察行为都必须由源语义允许。行为应记录终止或发散,不能只比较终止时的返回值;否则把正常返回的程序编译成死循环也可能漏检。若源语言含未定义行为(UB),还须约定 UB 之后允许哪些行为:可以将其后续按“任意行为”闭合,或改写为显式的行为改进关系。CompCert 手册 §1.2 使用后一种表述,允许消除源程序的运行时错误,但对无错误源程序保留其允许的终止、发散与 I/O 行为。
验证器可以不完备,即对某些其实正确的输出拒绝;流水线此时应回退、重编译或报告失败,绝不能把 reject 当作“可能也没问题”继续发布。若用小步语义检查局部模拟,证书通常给出控制点映射、状态关系、数据流事实或每条目标边的源路径见证。
验证器本身的可信边界必须明示。可在证明助理中证明
为了让这条蕴含可部署,
直觉
与其证明一座巨大工厂永远不会装错零件,翻译验证在每件成品出厂时核对它与订单。优化器可以使用复杂启发式甚至外部求解器,只要每次输出都附带足够证据,简单检查器就能确认这一个源—目标对没有新增行为。
这种分工把“找到优化”和“检查优化”分开。前者可不完备、依赖性能技巧,后者力求小、确定且有可靠性证明。它特别适合搜索算法复杂、但候选结果的验证条件清晰的阶段,例如寄存器着色、指令调度或局部等价检查。
例子与边界
取字宽
若优化器误产出
边界还包括算法覆盖率。一个只理解常量折叠的验证器可能拒绝完全正确的循环展开,这是只影响完备性的假阴性。真正危险的是破坏 soundness 的假阳性:checker 接受错误翻译。随机测试源、目标在若干输入相等可以协助筛错,却没有给出对所有输入和路径的可靠性定理。
一个能完整运行的有限位算术验证器
下面把通用契约收紧到可以穷举的片段,给出验证器的全部义务。它不需要证书,等价于取
值类型只有
程序是有限基本块表和一个入口。每个变量有固定声明类型,每条普通指令为
算术与比较的两个输入均为 goto L、br b L R 或 return x,其中
良构检查不能只查操作码
Check(P) 依次完成以下有限检查,任何失败都返回拒绝原因。
- 检查语法、变量和块标识唯一声明、正字宽、合法常量、操作码和指令类型;输入声明及返回类型与共同接口一致。
- 入口标识必须属于块表,因此块表非空;每个跳转目标都存在。由入口遍历能到达全部块。对全部边做拓扑排序,若有环则拒绝。没有后继的块必须以 return 结束,不能默默掉到文件末尾。
- 按拓扑次序检查每次读取前已确定赋值。入口的已定义集合为输入变量。其他块
的入口集合为
扫描普通指令时,先检查全部右侧变量在当前集合,再加入左侧;末尾分支和返回的读取也要检查。所得集合即
这套 definite-assignment 检查可以保守地拒绝只在不可行语法路径上未赋值的程序;本页接受的片段明确定义为通过上述检查的程序,不宣称识别所有语义上安全的图。
执行引理。 对每个通过 Check 的程序和每个合法输入,Run(P,input) 都有且只有一个返回值。证明按拓扑执行路径归纳:入口输入有值;若实际从
可以把这一引理对应的解释器写成
Run(P, input): // P has already passed Check
env := bind(P.inputs, input)
block := P.entry
repeat:
for instr in P[block].ordinary:
env[instr.dst] := EvalPrimitive(instr, env, P.width)
match P[block].terminator:
return x: return env[x]
goto L: block := L
br b L R: block := (L if env[b] else R)
EvalPrimitive 对上面七类指令逐项解释,算术写回时取模;程序里的重复循环是解释器控制结构,不表示被解释图含环。终止证明来自拓扑编号,不能以任意燃料耗尽后返回一个默认数字代替它。
验证器与接受可靠性
固定词典序枚举全部
Validate(P, Q):
if interfaces differ: return reject("interface")
if Check(P) fails or Check(Q) fails:
return reject("outside the checked fragment")
for input in all tuples in B_w^k, in lexicographic order:
source := Run(P, input)
target := Run(Q, input)
if source != target:
return reject(input, source, target)
return accept
定理是
任取
反过来,对两个通过 Check 且同接口的程序,若所有输入返回值都相等,则枚举从不走拒绝分支,最终接受。因此该验证器在所声明片段内既可靠又完备。通用程序等价的不可判定性不妨碍这个有限结果;若把片段换成一般程序,不能再照搬此结论。[1]
十六个输入中的溢出反例
取 1,3,7,15 都是四位常量;为简洁,立即数可直接出现在算术右侧,等价于先用常量指令绑定。
P.E: y := x + 1; b := (y <_u x); br b T F
P.T: return 7
P.F: z := y * 3; return z
同样,return 7 是把常量绑定到新临时量再返回的简写。因为
Q.E: b := (x = 15); br b T F
Q.T: return 7
Q.F: t := x * 3; z := t + 3; return z
加法和乘法同在模16环中,
| 输入 |
源分支 | |||
|---|---|---|---|---|
| 0 | 1 | F | 3 | 3 |
| 1 | 2 | F | 6 | 6 |
| 7 | 8 | F | 8 | 8 |
| 8 | 9 | F | 11 | 11 |
| 14 | 15 | F | 13 | 13 |
| 15 | 0 | T | 7 | 0 |
表只展示六项,验证器实际检查全部16项,首次差异为 (input=15, source=7, target=0)。这同时说明边界输入必须进入枚举、无符号比较不能当作整数不溢出比较,以及相同 CFG 不是接受的前提:错误候选即使只剩一个块,仍可解析、检查和拒绝。
另一种拒绝发生在执行之前。例如目标的一条分支跳往不存在的块,或在某个前驱没有给返回变量赋值,Check 就拒绝;这不是一个“两个返回值不等”的见证。应区分结构/接口不支持与已找到语义反例,两个理由都不能作为发布许可。
证明与实现的信任边界
上述证明覆盖的是给定的数学检查器与解释器。具体实现还需忠实完成解析、字宽与模运算、CFG 和赋值检查、完整元组枚举及结果比较,并确保最终使用的正是检查过的程序对象。若用宿主有符号溢出、错误移位或浮点数代替精确位运算,正文的代数证明不能补救实现偏差。
循环交换的实际IR检查器提供另一种已限定接口:固定尺寸和视图,重新核全部实例一一覆盖与数组冲突边前向,利用相邻独立交换证明对所有整数初始内存保持终态。它不枚举内存值;目标循环、尺寸或视图变更后须重新验证。循环分块还必须生成并核查min裁边,不能用覆盖正确代替跨块顺序正确。
实际验证器可用符号执行与求解器缩短穷举;这会引入新的公式生成和求解结果可信性义务。预算耗尽只能拒绝或返回明确的未知结果,不能接受。工业 IR 中的内存、poison、UB、异常、外部调用与循环也要各自进入语义与证明;这里给出的16输入检查没有声称覆盖它们。文献中的经过证明的验证器,进一步把检查器的可靠性在证明助理中机械化;本页的纸面证明与可复算算法不冒称已做那项机械化工作。[1]
推论与应用
验证器可采用前向模拟证书、后向模拟证书、SMT 可证等价或专用校验条件。不同方法仍要落到同一观察契约。验证多个阶段时,可以逐阶段检查并组合精化,也可对源与最终目标做全局验证;前者定位清楚,后者可能发现跨阶段对应,但证书更难生成。
逐阶段组合还要求阶段一的目标语义与阶段二当作源语义的版本完全一致,包括整数宽度、外部事件、调用约定和 UB 解释。两个 checker 即使各自可靠,若通过不同解析器理解同一中间文件,集合包含也无法首尾相接。
参考资料
-
Amir Pnueli, Michael Siegel, and Eli Singerman, “Translation Validation,” in TACAS 1998, LNCS 1384, Springer, pp. 151–166.
-
George C. Necula, “Translation Validation for an Optimizing Compiler,” PLDI, 2000, pp. 83–94.
-
[1] Jean-Baptiste Tristan and Xavier Leroy, “Formal Verification of Translation Validators: A Case Study on Instruction Scheduling Optimizations”, POPL, 2008, pp. 17–27,§§1–2.1:接受蕴含正确翻译的契约与已验证检查器。本文有限穷举片段为独立教学构造,并非该论文的指令调度算法。
-
The CompCert project, CompCert C: a trustworthy compiler, §1.2:行为改进契约、未定义行为,以及终止、发散和 I/O 的观察约定。
-
[2] SMT-LIB, FixedSizeBitVectors 理论,语义部分的
nat2bv、bvadd、bvmul与bvult:固定字宽模算术和无符号比较。