强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

LLVM 开发指南 / 第 19 章:故障排查

第 19 章:故障排查

“当你在 LLVM 中遇到问题时,你并不孤单——几乎每个 LLVM 开发者都踩过同样的坑。”


19.1 IR 验证

19.1.1 使用 opt 验证

# 验证 IR 文件
opt -passes='verify' input.ll -o /dev/null

# 每个 Pass 后都验证
opt -passes='instcombine,gvn' -verify-each input.ll -o output.ll

# 查看错误
opt -passes='verify' bad.ll 2>&1

19.1.2 常见 IR 验证错误

错误: "Instruction does not dominate all uses!"
原因: 使用了未支配的 SSA 值
修复: 检查 PHI 节点和基本块顺序

错误: "Referencing function in a different module"
原因: 跨模块引用
修复: 使用 llvm-link 合并模块

错误: "Operand is null"
原因: 指令操作数为空
修复: 检查 IR 生成代码

错误: "Basic Block has no terminator"
原因: 基本块没有终止指令
修复: 确保每个基本块以 br/ret/... 结尾

错误: "Multiple terminating instructions"
原因: 基本块有多个终止指令
修复: 删除多余的终止指令

错误: "Redefinition of function"
原因: 函数重复定义
修复: 使用不同的名称或链接类型

19.1.3 IR 验证工具

# 使用 llvm-reduce 自动缩减 bug 用例
# 创建测试脚本
cat > check.sh << 'EOF'
#!/bin/bash
! opt -passes='my-pass' $1 -o /dev/null 2>&1 | grep "error"
EOF
chmod +x check.sh

llvm-reduce --test=check.sh input.ll -o reduced.ll

19.2 Pass 调试

19.2.1 打印 Pass 流水线

# 查看 O2 包含哪些 Pass
opt -passes='default<O2>' -print-pipeline-passes input.ll -o /dev/null 2>&1

# 查看 Pass 执行顺序
opt -passes='default<O2>' -debug-pass-manager input.ll -o /dev/null 2>&1

19.2.2 调试选项

# 打印每个 Pass 前后的 IR
opt -passes='instcombine' -print-before-all -print-after-all input.ll -o /dev/null 2>&1

# 只打印特定 Pass
opt -passes='instcombine' -print-before=instcombine -print-after=instcombine input.ll -o /dev/null 2>&1

# 打印修改过的 IR
opt -passes='instcombine' -print-changed input.ll -o /dev/null 2>&1

# 打印统计
opt -passes='instcombine' -stats input.ll -o /dev/null 2>&1 | head -20

19.2.3 使用 GDB 调试 Pass

# 调试 opt
gdb --args opt -passes='my-pass' input.ll -o /dev/null

# 在 Pass 中设置断点
(gdb) break MyPass::run
(gdb) run

# 调试 Clang
gdb --args clang -O2 test.c -o test
(gdb) break InstCombinePass::run
(gdb) run

19.3 代码生成调试

19.3.1 查看各阶段 MIR

# 查看指令选择后
llc -stop-after=instruction-select input.ll -o isel.mir

# 查看寄存器分配后
llc -stop-after=regalloc input.ll -o regalloc.mir

# 查看所有阶段
llc -print-after-all input.ll -o /dev/null 2>&1 | less

# 使用 llc debug 输出
llc -debug-only=isel input.ll -o /dev/null 2>&1
llc -debug-only=regalloc input.ll -o /dev/null 2>&1

19.3.2 常见代码生成问题

问题: "Cannot select: ..." (指令选择失败)
原因: DAG 中有无法匹配的节点
解决: 检查目标后端的 TableGen 定义

问题: "ran out of registers during register allocation"
原因: 寄存器溢出过多
解决: 减少变量活跃范围,或使用更多寄存器

问题: 生成的代码效率低
解决: 1. 检查 -O2 是否启用
       2. 检查目标特性是否正确
       3. 使用 -Rpass 检查优化报告

19.4 Clang 编译问题

19.4.1 常见错误

# 找不到 LLVM 头文件
# 错误: fatal error: 'llvm/IR/Module.h' file not found
# 解决: 安装 llvm-dev 或设置正确的 include 路径

# 链接错误
# 错误: undefined reference to 'llvm::...'
# 解决: 使用 llvm-config --libs 获取正确的链接库列表

# 版本不匹配
# 错误: LLVM ERROR: Building for an unexpected version
# 解决: 确保所有 LLVM 库版本一致

19.4.2 诊断信息

# 查看编译详细过程
clang -v test.c -o test

# 查看预处理输出
clang -E test.c

# 查看汇编输出
clang -S test.c -o test.s

# 查看 LLVM IR
clang -S -emit-llvm test.c -o test.ll

# 查看优化报告
clang -O2 -Rpass='.*' test.c 2>&1
clang -O2 -Rpass-missed='.*' test.c 2>&1
clang -O2 -Rpass-analysis='.*' test.c 2>&1

19.5 内存和性能问题

19.5.1 编译时间分析

# 使用 -ftime-report
clang -O2 -ftime-report test.c -o test 2>&1 | head -40

# 使用 -ftime-trace (Chrome trace 格式)
clang -O2 -ftime-trace test.c -o test
# 生成 test.json,可在 chrome://tracing 查看

# 使用 opt 的时间统计
opt -O2 -time-passes input.ll -o output.ll 2>&1

19.5.2 内存使用分析

# 编译时内存使用
# 使用 /usr/bin/time
/usr/bin/time -v clang -O2 large_file.c 2>&1 | grep "Maximum resident"

# 减少内存使用
# 减少并行编译数
ninja -C build -j2  # 减少并行数

# 对于 opt/llc
ulimit -v 4000000   # 限制虚拟内存

19.6 JIT 调试

# ORC JIT 调试
LLVM_JIT_DUMP=1 ./my_jit_program

# 使用 GDB 调试 JIT 代码
# 需要注册 GDB JIT 接口
# 注册后可以在 JIT 编译的函数上设置断点

19.7 测试用例最小化

19.7.1 使用 llvm-reduce

# 创建测试脚本
cat > test_bug.sh << 'EOF'
#!/bin/bash
# 返回 0 表示 bug 仍然存在
clang -O2 $1 -o /tmp/test 2>&1 | grep "LLVM ERROR"
EOF
chmod +x test_bug.sh

# 自动缩减
llvm-reduce --test=test_bug.sh input.c -o reduced.c

19.7.2 使用 creduce

# 安装 creduce
sudo apt install creduce

# 使用
creduce test_bug.sh input.c

19.8 常见问题 FAQ

问题原因解决方案
LLVM ERROR: Broken functionIR 验证失败-verify-each 定位问题
Cannot select指令选择失败检查 TableGen 定义
Ran out of registers寄存器分配失败减少变量活跃范围
Segmentation faultLLVM 内部 bug检查输入 IR 是否合法
undefined reference链接库缺失llvm-config --libs
ABI mismatch库版本不匹配统一 LLVM 版本
Slow compilation编译时间过长检查 Pass 流水线
编译后代码慢未启用优化使用 -O2
ninja: error构建失败检查 CMake 配置

19.9 调试工具总结

工具用途
opt -verify-each每个 Pass 后验证 IR
opt -print-after-all打印每个 Pass 后的 IR
opt -debug-pass-manager查看 Pass 执行顺序
opt -time-passesPass 执行时间
llc -debug-only=regalloc调试寄存器分配
clang -ftime-report编译时间分析
llvm-reduce自动缩减测试用例
gdb调试 LLVM 代码
valgrind检测内存问题

19.10 本章小结

场景推荐工具
IR 错误opt -verify-each
Pass 问题opt -print-after-all
代码生成llc -stop-after=
编译时间-ftime-report
测试缩减llvm-reduce
内存问题/usr/bin/time -v

扩展阅读

  1. LLVM FAQ — 常见问题
  2. LLVM Bugpoint — 自动 bug 缩减
  3. LLVM Debugging Tips — 调试技巧

下一章: 第 20 章:最佳实践与贡献指南 — LLVM 代码规范、社区贡献和生产应用。