为什么需要LLVM IR 转SMT公式
LLVM IR中有两个特殊的值:
- undef:表示"未定义",可以是任意值,而且每次读取都可能得到不同的值。
- poison:表示"毒化",是更受限的未定义,通常由算术溢出等操作产生,一旦使用就会导致UB(未定义行为)。
在验证优化时,我们必须精确地处理这些特殊值,否则可能会把正确的优化误判为错误,或者反之。SMT编码就是用数学逻辑来描述这些语义。
编码一个寄存器的值
对于每个虚拟寄存器(比如 %a),我们用一个有序对来表示它的状态:
text
R[%a] = (实际值, is_poison)
- 实际值:如果该寄存器当前是
undef,则用一个新创建的SMT变量(如undef_1)表示;否则就是它原本的LLVM值(也可能是poison,但poison单独用标志表示)。 - is_poison:布尔值,表示该寄存器当前是否处于
poison状态。
例子:简单函数
考虑以下LLVM函数
llvm
define i32 @test(i32 %a) {
%t = add i32 %a, %a ; t = a + a
%c = icmp eq i32 %t, 0 ; c = (t == 0)
%q = shl i32 %a, 2 ; q = a << 2
%r = and i32 %a, 1 ; r = a & 1
ret i32 %r ; 注意这里返回的是%r,但原文有分支
}
改成带分支
llvm
define i32 @test(i32 %a) {
%t = add i32 %a, %a
%c = icmp eq i32 %t, 0
br i1 %c, label %then, label %else
then:
%q = shl i32 %a, 2
ret i32 %q
else:
%r = and i32 %a, 1
ret i32 %r
}
现在按照文本中的方式编码每个寄存器(假设%a不是undef也不是poison,仅用于展示):
%a:R[%a] = (ite(is_undef_a, undef_1, %a), is_poison_a) 意思是:
- 如果%a是undef(is_undef_a为真),则实际值取新变量undef_1;否则取原值%a。同时用一个布尔is_poison_a记录它是否是poison。
%t = add %a, %a :加法结果的实际值 = ite(is_undef_t1, undef_2, R[%a].val) + ite(is_undef_t2, undef_3, R[%a].val) -
- 即如果某个操作数是undef,就引入新变量
- 否则用该寄存器的实际值。
- 结果的is_poison标志根据加法规则传播(例如如果任何操作数是poison或加法溢出则设为poison)。
%c = icmp eq %t, 0:LLVM中布尔用i1表示,所以%c的实际值是一个ite:
- ite( (R[%t].val == 0), 1, 0 ) 同时其is_poison标志取决于%t是否是poison。
%q = shl %a, 2:
- 原文说"由于非负定义值上分支是UB,我们可以假设%t是负定义的,进而%a也是负定义的"。
- 这里涉及优化:因为shl要求左移计数必须小于位宽,否则是UB。
- 但编码中可能忽略undef/poison情况,认为%a已经是非负定义值(即不是undef也不是poison),所以直接编码为shl(%a, 2)且is_poison=false。
- 这正是文本中"忽略了%a是undef或poison的情况"的含义。
%r = and %a, 1:
and操作:实际值 =R[%a].val & 1,is_poison传播。
编码函数的最终状态
函数可以有多个返回点,也可能永不返回(如无限循环或noreturn函数)。因此最终状态需要编码:
- 返回值(retval):是一个ite树,根据控制流路径选择对应返回指令的值。
- ub:表示是否存在未定义行为(如使用了poison或执行了UB操作)。
- noret:布尔值,表示函数是否可能不返回(例如所有路径都陷入无限循环)。
对于上述分支函数,返回值可以写成:
text
retval = ite( %t == 0,
R[%q].val, // then分支的返回值
R[%r].val ) // else分支的返回值
同时ub和noret分别根据路径条件组合。
举例说明优化
假设我们分析得知,当%t == 0时,shl %a, 2操作是UB(比如因为%a是poison或者移位计数越界),那么整个then分支的行为就是UB。
因此,在最终的ite表达式中,我们可以直接省略该分支的贡献,甚至将整个返回值简化为else分支的值,因为UB情况下的返回值无意义。
这种简化能大大降低SMT求解器的复杂度,使验证更可行。