SMT 是什么?
SMT(Satisfiability Modulo Theories,基于模理论的可满足性)是逻辑学与计算机科学中的一个领域。
它研究的是:在某种背景理论(如整数运算、数组、位向量、指针等)下,判断一个一阶逻辑公式是否存在满足所有条件的解(即是否可满足)。
简单说:SMT = 命题逻辑 + 理论(如算术、位运算、数组等)。
SMT 求解器(如 Z3、CVC5)可以自动找出满足公式的变量赋值,或证明不存在这样的赋值。
例子
公式x + 2 = 5 ∧ x > 0在整数理论下是可满足的,解 x = 3。
公式 x + 2 = 5 ∧ x < 0 不可满足。
Z3 简介
Z3 是微软开发的高性能 SMT 求解器,支持多种理论(整数、实数、位向量、数组、未解释函数等)。
它提供了 API(C++、Python、.NET 等),可以编程构造公式并求解。
使用 Z3 验证特定输入下优化是否正确
你要区分两种验证场景
全程序正确性验证 (所有可能的输入): 需要证明 ∀ inputs, P_original(inputs) = P_optimized(inputs) 。
这是非常困难的,通常需要归纳或循环不变式,且 SMT 求解器可能无法直接处理(循环需要量词或展开)
特定输入验证(测试一个具体用例):给定具体输入值(例如 x = 5, y = 10),检查优化后的代码是否产生相同输出。这只需要将输入具体化,然后分别计算两个程序的结果并比较。对于有界循环(已知循环次数),可以直接展开为无环代码,然后交给 Z3 计算。
你问的是第二种:对特定的输入程序(特定函数)进行验证,而非验证所有输入 。这本质上是一种测试生成 或符号执行,Z3 在这里的作用是求解具体的输入值是否会导致差异(反例),而不是证明通用等价性。
典型用法:使用 Z3 寻找反例
假设你想验证一个优化是否正确。你构造一个公式,其可满足意味着"存在某个输入使得原始程序与优化后程序行为不同"。
如果 Z3 找到这样的输入,则优化有 bug;如果不可满足,则对于所有输入都等价(但仅当你的编码覆盖了所有可能输入时才是全称证明)。
但如果你只想验证特定输入,更简单:直接计算。不过 Z3 可以帮助你生成这样的输入(比如随机测试失败时,用 Z3 缩小范围)。
具体例子:使用 Z3 验证一个简单优化
假设原始 C 函数:
c
int f(int a, int b) {
return a + b;
}
优化后
c
int g(int a, int b) {
return b + a;
}
我们想验证对于特定输入 a = 3, b = 5,优化是否正确。这太简单了,直接计算即可。但更实用的场景是:我们不知道哪个输入会暴露 bug,希望 Z3 找出一个反例。
步骤(使用 Z3 Python API):
- 构造符号变量表示程序输入。
- 编码原始程序和优化程序的语义(用 Z3 的位向量或整数运算)。
- 断言输出不相等。
- 求解。如果找到解,就是反例;如果 unsat,则对所有输入等价(因为编码没有限制输入范围)。
注意:这里的"所有输入"取决于你如何声明变量。如果只声明整数变量,则 Z3 会考虑所有数学整数(无限)。如果只关心有界整数(如 32 位),可以用位向量。
代理示例
python
from z3 import *
# 32 位整数变量
a = BitVec('a', 32)
b = BitVec('b', 32)
# 原始程序
orig = a + b
# 优化程序
opt = b + a
# 断言两者不同
s = Solver()
s.add(orig != opt)
# 检查是否存在反例
if s.check() == sat:
model = s.model()
print("Counterexample found:")
print("a =", model[a])
print("b =", model[b])
else:
print("No counterexample: optimization is correct for all 32-bit inputs")
输出:unsat,说明加法交换律成立,优化正确
如何处理循环(有限次数)
如果程序包含循环,但循环次数有界(例如由输入参数决定,但具体调用时已知固定次数),你可以展开循环。
例如:原始程序:
c
int sum(int n, int *arr) {
int s = 0;
for (int i = 0; i < n; i++) s += arr[i];
return s;
}
对于特定输入 n = 3,我们可以手动展开为:
c
s = 0;
s += arr[0];
s += arr[1];
s += arr[2];
然后用 Z3 的数组理论编码内存访问,比较原始展开和优化展开的结果。
Z3 支持数组(Array)和 Select/Store 操作,可以建模有限的内存访问。对于有界循环,展开后得到无环的 SSA 形式,交给 Z3 求解。
例子:验证循环展开优化
假设优化将 sum 循环替换为直接使用 SIMD 指令,你只想测试 n = 3 时是否正确。用 Z3 构造:
- 符号化数组 arr(长度 3)。
- 分别计算原始展开结果和优化结果。
- 断言不等,求解。
如果 unsat,则对于该长度下所有可能的数组内容,优化都正确。
总结
| 概念 | 解释 |
|---|---|
| SMT | 在背景理论下判断公式是否可满足 |
| Z3 | 一个流行的 SMT 求解器 |
| 验证特定输入 | 将输入具体化为常量,直接计算并比较;或构造公式寻找反例 |
| 有限循 | 展开为无环代码,再用 Z3 验证 |
| 验证所有输入 | 需要使用全称量词 ∀,Z3 也能处理但更困难,且可能不终止 |
使用 Z3 验证特定输入下的优化正确性,本质上是有界验证或具体执行测试。
它不能证明通用正确性,但可以高效发现反例,并且对于循环次数固定的情况非常实用。
如果你需要证明对所有输入正确,则需要更强的技术(如循环不变式、归纳法、或使用更高级的验证工具如 CBMC、Crucible 等)。