unreachable 是 llvm ir 中表示基本块永不正常执行到末尾的终端指令,用于向优化器传递不可达语义,常见于异常抛出、显式终止调用及编译器推导的死路径。

unreachable 指令在什么控制流路径上出现
unreachable 是 LLVM IR 中的终端指令(terminator),表示该基本块(basic block)**永远不会正常执行到末尾**。它不生成任何机器码,只向优化器和后端传递“此处不可达”的语义信号。常见于:
• throw 之后的代码(如 C++ 异常抛出点后)
• abort()、__builtin_unreachable() 等显式终止调用之后
• 编译器推导出的死路径(例如 if (false) { ... } 分支)
• switch 或 br 的默认分支中,当所有可能 case 已被穷举且无 fallthrough 时
为什么编译器会插入 unreachable 而不是直接删掉代码
LLVM 不会在 IR 生成阶段就激进删除“显然不可达”的代码,而是先保留完整控制流结构,再交由后续 pass 处理。unreachable 提供了比简单删除更精确的语义:它明确告诉 dead-code-elimination、simplifycfg、licm 等优化 pass,“这条路径逻辑上不存在”,从而支持更安全的跨基本块分析。比如:
• mem2reg 可跳过对 unreachable 块中 alloca 的 PHI 合并
• tailcallelim 不会尝试将调用优化为跳转,如果目标块以 unreachable 结尾
• 后端生成代码时,可直接跳过该块,避免生成冗余指令或调试信息
常见误用:把 unreachable 当作 assert 或错误处理
unreachable 不是运行时检查,也不触发任何异常或日志。它只是编译期断言。以下写法是危险的:
• 在可能被用户输入触发的路径里硬编码 unreachable(应改用 call void @abort() 或条件分支)
• 期望它在 debug build 中报错 —— 实际上即使启用 -O0,它也**不会插入任何检查逻辑**
• 和 assume 混用却不保证前提成立,导致未定义行为被传播到上游优化中
正确做法是:仅用于编译器能静态证明为假的路径;若需运行时保障,请用 llvm.trap 或显式函数调用
查看和验证 unreachable 是否合理
用 opt -print-after-all 或 opt -passes='print<cfg>'</cfg> 观察 IR 流程中 unreachable 出现的位置是否符合预期。特别注意:
• 它不应出现在函数入口基本块(entry block)的首条指令
• 它前面不能有非 terminator 指令(否则 IR 验证失败:Invalid instruction in basic block)
• 若它出现在 invoke 后但没配对 landingpad,可能意味着异常传播逻辑缺失,Clang 通常会拒绝生成这样的 IR
一个典型合法例子是 f2 函数末尾:call void @__cxa_throw(...) 后紧跟 unreachable —— 因为 __cxa_throw 不返回,该路径确实终结











