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求解器的复杂度,使验证更可行。

相关推荐
一个心烑2 小时前
奖项届定获取方式
java
小红的布丁2 小时前
Reactor 模型详解:单 Reactor、主从 Reactor 与 Netty 思想
android·java·开发语言
weixin_704266052 小时前
redis 的集群
java·数据库·redis
被摘下的星星2 小时前
Java的类加载
java·开发语言
真上帝的左手2 小时前
8. 测试-性能测试-JMeter实战
java·压力测试
cheems95272 小时前
[SpringMVC] SpringWebMVC常见注解介绍
java·springmvc·注解
me8322 小时前
【Java】Spring MVC接口执行流程详解:从前端请求到参数封装全解析(前端到底是怎么和后端交互的?)
java·spring·mvc
skilllite作者2 小时前
SkillLite 多入口架构实战:CLI / Python SDK / MCP / Desktop / Swarm 一页理清
开发语言·人工智能·python·安全·架构·rust·agentskills
niucloud-admin2 小时前
插件开发——upgrade 插件版本升级
java