LLVM内联需满足三重条件:先通过函数特征过滤(如无异常、单入口等),再达阈值要求(如cost≤threshold),最后IR合法且Pass正确加载;任一失败即静默跳过,可用-mllvm -print-inlining验证是否进入决策流程。

看编译器是否真的尝试内联了
LLVM 不会在没触发条件时“凭空”内联,首先要确认它有没有走内联逻辑。最直接的办法是加 -mllvm -print-inlining(Clang 下)或用 opt -passes='print<inliner>'</inliner>(对 .bc 文件)。输出里出现类似 Considering inlining foo into bar (cost=15, threshold=325) 才算真正进了决策流程;如果一行都没,说明函数连候选资格都没过——常见原因是函数定义不在当前 TU、有异常处理、含跳转表、或被 __attribute__((noinline)) 显式禁止。
检查函数是否通过了 LLVM 的基本过滤
LLVM 内联前会做硬性检查,任一失败就直接跳过:
- 函数是否有未处理的异常区域(
hasEHRanges()为 true)?C++ 异常、setjmp/longjmp 都会拦住 - 是否是多入口函数(
isMultiEntry())?比如带多个 entry block 的 coroutine 或手写 IR - 是否用了
getelementptr访问未声明的全局变量?IR 类型不匹配会导致getInliningInfo()返回空 - 函数体是否为空或只含
ret void?这类会被优化掉,根本不会进内联队列
这些检查在 getInliningInfo() 中完成,不满足就静默跳过,不会报错也不会警告。
确认阈值和调用上下文是否达标
即使函数“合格”,LLVM 还要算一笔账:内联收益是否够本?默认阈值受多个参数影响:
-
-inline-threshold=225(Clang 默认)是主开关,实际 cost = 函数指令数 × 调用频次权重 - 小函数(
-inline-small-functions)单独放宽到 4 字节指令长度,但仅限无循环、无复杂控制流的函数 - 调用点本身在冷路径(如
if (unlikely(...))分支里),LLVM 会降权甚至跳过 - 若函数被取地址(
&foo)、或跨 TU 调用且没开 LTO,内联直接禁用
用 opt -passes='print<inliner>' -disable-output input.bc</inliner> 可看到每处调用点的 cost 和 threshold 对比,这是定位“为何不内联”的关键证据。
验证 IR 合法性与 Pass 加载时机
很多“内联没生效”其实是 IR 本身不合规,导致 Inliner Pass 在访问函数时提前退出:
- 先用
llvm-as input.ll -o input.bc确保语法正确 - 再跑
opt -verify -disable-output input.bc检查 IR 结构合法性(比如undef被当实值用、getelementptr索引越界) - 如果用自定义 Pass,确保它没在
runOnFunction里误改了函数签名或 CFG,否则 Inliner 可能因函数状态不一致而跳过 - 用
llc -march=host input.bc -o /dev/null测试后端兼容性——llc 报错说明 IR 已损坏,Inliner 不会负责兜底
IR 错误往往不报错,而是让 Inliner 收到一个无效的 Function*,于是 quietly 忽略该调用点。这点最容易被忽略,尤其在手写或 JIT 生成 IR 的场景里。

















