Hotdry.
compilers

玩具优化器模糊测试:属性基生成随机IR并验证pass不变量

用属性基模糊测试生成随机Toy IR程序,运行解释器比较优化前后堆状态,捕获load/store优化bug,提供生成参数、不变量与CI集成要点。

在编译器优化器开发中,手工测试难以覆盖边缘 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 字)

查看归档