LLVM IR 转 SMT公式

为什么需要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分支的返回值

同时ubnoret分别根据路径条件组合。

举例说明优化

假设我们分析得知,当%t == 0时,shl %a, 2操作是UB(比如因为%a是poison或者移位计数越界),那么整个then分支的行为就是UB。

因此,在最终的ite表达式中,我们可以直接省略该分支的贡献,甚至将整个返回值简化为else分支的值,因为UB情况下的返回值无意义。

这种简化能大大降低SMT求解器的复杂度,使验证更可行。

相关推荐
Flittly11 小时前
【AgentScope Java新手村系列】(16)从RAG到多路检索
java·spring boot·spring
小兔崽子去哪了11 小时前
Java 生成二维码解决方案
java·后端
人活一口气16 小时前
从JVM调优到MCP协议:Java全栈技术体系深度总结与企业级架构实践
java·spring boot
NE_STOP17 小时前
Vibe Coding -- 完整项目案例实操
java
荣码17 小时前
GraphRAG:普通RAG只能回答"点"的问题,我踩了4个坑才搞懂
java·python
SimonKing17 小时前
Google第三方授权登录
java·后端·程序员
明月光81818 小时前
从一行 @Builder 说起:重新拾起 Java 的 Lombok、注解与 Builder 模式
java
考虑考虑1 天前
Mybatis实现批量插入
java·后端·mybatis
咖啡八杯1 天前
GoF设计模式——中介者模式
java·后端·spring·设计模式
青石路1 天前
记一次多JDK版本问题的排查,一坑套一坑,差点没爬上来
java