在编译器优化器开发中,手工测试难以覆盖边缘 case,尤其是多 pass 交互。Max Bernstein 在 Toy Optimizer 系列中开发了一个 DIY 模糊测试器,通过属性基方法生成随机 IR 程序,验证 load/store 优化 pass 的正确性。该方法利用随机输入与 pass 不变量(如语义保持),高效捕获 bug,如别名失效导致的堆状态错误。
Toy Optimizer 是一个小型 SSA IR 优化器,专注于 load/store forwarding 与 TBAA(类型基别名分析)。IR 操作包括 getarg、load (obj, offset)、store (obj, offset, value)、escape (value)。模糊测试的核心是生成随机基本块(basic block),模拟真实优化场景。
生成器简单高效:从 3 个 getarg 开始,循环随机选择 load/store/escape(最多 30op)。load/store 使用随机 offset(0-4)和 value(0-10),escape 随机选有输出 op,确保观测点。代码如下:
def generate_program():
bb = Block()
args = [bb.getarg(i) for i in range(3)]
num_ops = random.randint(0, 30)
ops_with_values = args[:]
for _ in range(num_ops):
op = random.choice(["load", "store", "escape"])
arg = random.choice(args)
a_value = random.choice(ops_with_values)
offset = random.randint(0, 4)
if op == "load":
v = bb.load(arg, offset)
ops_with_values.append(v)
elif op == "store":
value = random.randint(0, 10)
bb.store(arg, offset, value)
elif op == "escape":
bb.escape(a_value)
return bb
此生成确保覆盖常见模式,如连续 store/load、多 obj 交互。参数选择:ops 上限 30 防爆炸;offset 小范围匹配 IR 假设(word-sized);value 小整数简化解释器比较。
验证依赖简单解释器模拟堆:dict [(obj:str, offset:int): value]。getarg 取输入,store 更新堆,load 读堆(缺省 "unknown"),escape 收集输出。运行两次:无别名输入 (["a","b","c"]) 与全别名 ([a,a,a]),比较优化前 / 后堆 + escaped 列表相等。
def interpret_program(bb, args):
heap = {}
ssa = {}
escaped = []
for op in bb:
# ... (详见原文)
heap["escaped"] = escaped
return heap
def verify_program(bb):
before_no_alias = interpret_program(bb, ["a", "b", "c"])
a = "a"
before_alias = interpret_program(bb, [a, a, a])
optimized = optimize_load_store(bb)
after_no_alias = interpret_program(optimized, ["a", "b", "c"])
after_alias = interpret_program(optimized, [a, a, a])
assert before_no_alias == after_no_alias
assert before_alias == after_alias
不变量:1. 语义保持(堆 /escaped 相同);2. 覆盖别名 / 非别名场景。测试循环:seed 固定(CI 用),跑 10 万程序。若禁用优化中别名失效(may_alias 检查),立即崩溃,diff 显示堆错(如 ('a',0):4 vs 9),完美 repro。
证据:在禁用 store 时别名失效,fuzzer 秒捕获,人工难想。Bernstein 测试确认优化正确,无 bug 挂起。类似 PyPy 用 SMT fuzzing,但此 DIY 简单,适合 toy 后端。
落地参数 / 清单:
- 生成:ops=10-50,offset=0-7(扩展),value=0-100;seed = 随机打印 repro。
- 不变量:堆相等、escaped 相等、无 "unknown"leak;加 TBAA info 随机分配 Array/String。
- 缩减:用 Hypothesis @given 生成,自动 shrink 最小 fail case。
- 监控:pytest -k random,超时 5s / 测试;CI 跑 1M 程序,存 fail seed/blob。
- 回滚:若新 pass fail 率 > 1%,暂停 merge;cov: op 分布直方图。
- 扩展:加 loop(unroll N 次)、SMT(Z3 编码约束)、multi-pass 链。
此方法成本低(单 pass 生成 / 验证),适用于 JIT/toy 后端。缺点:无控制流 miss 深 bug,生成偏 simple;补以 hand-test + 全程序 suite。
资料来源:
(正文约 1200 字)