不建议自行开发编译器插件做dce,除非深度掌握llvm ir的cfg与dfa且有特殊裁剪需求;主流工具链(如clang的-fdata-sections -ffunction-sections + --gc-sections)已覆盖95%场景,更可靠、无误删风险。

直接上结论:不建议自行开发编译器插件来做 Dead Code Elimination(DCE),除非你已深度掌握 LLVM IR 控制流图(CFG)与数据流分析(DFA),且项目有特殊裁剪需求(如硬实时系统中必须剔除所有未显式调用的中断服务例程)。现代主流工具链(Clang + --gc-sections、Rollup、Closure Compiler、Rust 的 strip + opt-level=3)已覆盖 95% 场景,自研插件反而易引入误删、链接失败或调试信息错乱。
为什么 Clang/LLVM 的 -fdata-sections -ffunction-sections + --gc-sections 更可靠
这是目前嵌入式与系统级项目最成熟、可验证的 DCE 路径,不依赖源码语义分析,仅基于符号可见性与节区引用关系做裁剪:
-
-fdata-sections和-ffunction-sections让每个函数/全局变量单独成节(section),避免链接器“整段吞下”未调用代码 -
--gc-sections在链接阶段扫描所有节区的引用链,自动丢弃未被任何存活符号引用的节 - 它不关心
if(false)或宏展开逻辑,只看“是否被汇编指令实际寻址”,因此无误删风险 - 配合
-Wl,--print-gc-sections可输出被剔除的节名,用于审计和回归比对
自定义 LLVM Pass 做 DCE 容易踩的坑
如果你仍需定制逻辑(比如按编译宏 FEATURE_X 动态标记死区),必须注意以下三点:
- 别在
FunctionPass中直接删 BasicBlock——LLVM IR 要求 CFG 连通,应改用removeUnreachableBlocks()辅助函数 - 静态 DCE 无法处理通过
dlsym()、std::function、虚函数表间接调用的代码;这类需运行时符号解析,必须留白或加 extern 声明 - 若项目含 C++ 模板实例化,
MyTemplate<int>::func()</int>可能未被直接调用但被 vtable 引用,强行删除会导致运行时崩溃 - 调试信息(DWARF)不会随代码块自动同步更新,删掉函数后需调用
DIBuilder::finalize(),否则gdb会 segfault
真正需要插件的边界场景与替代方案
只有当标准工具链无法满足如下任一条件时,才值得投入开发:
- 要求按运行时配置(如 JSON 配置文件)决定哪些模块可裁剪——此时应改用构建时预处理器(
#ifdef MODULE_A)+-DMODULE_A编译标记,而非运行时分析 - 需识别“仅被测试代码调用”的函数并剔除(生产镜像中不带单元测试桩)——可用
clang++ -DTESTING=0配合宏卫士,比插件更轻量 - 要剔除特定注释标记的代码块(如
// @dce:always)——优先用 Clang 的 AST Matchers +clang-query写脚本提取节点,再交由外部工具生成剔除补丁,不侵入编译流程
最常被忽略的一点:DCE 效果必须绑定到具体目标平台。ARM Cortex-M4 上 -mthumb 指令集会让某些内联汇编块变成不可达,但 x86_64 下完全可达——插件若未做 TargetMachine 绑定,跨平台构建时会失效。










