ADCEPass比DCEPass更适合删除无副作用指令,因其不仅检查指令是否被使用,还主动分析mayWriteToMemory()、mayThrow()等副作用属性,并通过反向数据流分析识别并删除所有无副作用且无用户的指令。

为什么 ADCEPass 比 DCEPass 更适合删无副作用指令
单纯调用 DCEPass 只能删掉“完全未被使用”的值(如未被读取的计算结果),但很多无副作用指令(比如 %x = add i32 1, 2 后面根本没用到 %x)仍会残留——因为 LLVM 默认不假设指令可随意删除,除非它明确知道该指令没有副作用(no side effect)且其结果未被使用。ADCEPass(Aggressive Dead Code Elimination)会主动做两件事:一是遍历所有指令并检查 mayWriteToMemory()、mayThrow() 等属性;二是反向追踪所有未被使用的值,只要满足“无副作用 + 无用户(no users)”,就直接删。这正是你想要的。
用 opt 命令行快速验证是否删干净
别急着写 Pass,先确认 IR 里哪些指令真能被删:
- 用
clang-14 -S -emit-llvm -O0 test.c -o test.ll生成未优化 IR - 插入一条明显无用的指令,比如在函数开头加:
%dead = add i32 42, 1,且后续不引用%dead - 运行:
opt-14 -adce -S test.ll -o cleaned.ll(注意是-adce,不是-dce) - 对比
cleaned.ll,确认%dead = ...行已消失;若还在,说明该指令被误判为有副作用(例如用了call @printf却没声明nounwind readonly)
自定义 Pass 中调用 ADCEPass 的正确姿势
如果你在写自己的 Pass 并希望顺手清掉中间产生的死指令,不能直接 new ADCEPass() 然后 runOnFunction()——LLVM 新 Pass 管理器要求显式注册依赖和调度顺序。更稳妥的做法是复用已有基础设施:
- 在你的 Pass 的
runOnFunction结尾,手动触发 ADCE:if (auto *ADCE = getAnalysisIfAvailable<adcepass>()) ADCE->runOnFunction(F);</adcepass> - 或者更推荐:在 PassManager 中按需插入,例如在你的转换 Pass 后立即加一行
PM.add(createAggressiveDCEPass());(旧管理器)或用registerModuleAnalyses+addPass(新管理器) - 注意:ADCE 不处理跨函数的死代码(如未调用的全局函数),那是
GlobalDCEPass的职责
容易忽略的副作用陷阱:volatile 和隐式内存访问
即使一条指令看起来只是算数运算,也可能因上下文变成不可删:
-
%p = load volatile i32* %addr:volatile显式禁止优化,ADCEPass会跳过 -
%x = call i32 @unknown_func():LLVM 默认认为外部函数可能有副作用,除非你在 IR 中加declare i32 @unknown_func() nounwind readnone -
store i32 0, i32* %ptr:哪怕%ptr是栈上临时地址,store 仍被视为有内存副作用,不会被 ADCE 删
真正安全可删的,只有纯计算、无内存访问、不抛异常、不依赖全局状态的指令——比如 %a = add i32 %b, %c 且 %a 无用户,且所在基本块没有 phi 边依赖它。

















