使用 -opt-bisect-limit 调试优化错误¶
简介¶
-opt-bisect-limit 选项提供了一种禁用所有高于指定限制的优化 pass 的方法,而无需修改 Pass Manager 的填充方式。此选项的目的是帮助追踪问题,即优化期间的不正确转换导致不正确的运行时行为。
此功能以选择加入的方式实现。可以安全跳过同时仍允许正确代码生成的 Pass 在执行优化之前调用一个函数来检查 opt-bisect 限制。必须运行或不修改 IR 的 Pass 不执行此检查,因此永远不会跳过。通常,这意味着分析 pass、在 CodeGenOptLevel::None 运行的 pass 以及寄存器分配所需的 pass。
-opt-bisect-limit 选项可以与任何工具一起使用,包括使用核心 LLVM 库进行优化和代码生成的前端(如 clang)。下面讨论了调用该选项的确切语法。
此功能并非旨在取代其他调试工具(如 bugpoint)。相反,当重现问题需要复杂的构建基础设施(这将使使用 bugpoint 不切实际)或当重现失败需要一系列难以使用 opt 和 llc 等工具复制的转换时,它提供了另一种行动方案。
入门指南¶
-opt-bisect-limit 命令行选项可以直接传递给 opt、llc 和 lli 等工具。语法如下
<tool name> [other options] -opt-bisect-limit=<limit>
如果使用 -1 值,该工具将执行所有优化,但对于每个可以跳过的优化,都会向 stderr 打印一条消息,指示与该优化关联的索引值。要跳过优化,请传递要执行的最后一个优化的值作为 opt-bisect-limit。所有索引值较高的优化都将被跳过。
为了将 -opt-bisect-limit 选项与提供 LLVM 核心库包装器的驱动程序一起使用,可能需要一个额外的前缀选项,如驱动程序所定义。例如,要将此选项与 clang 一起使用,必须使用 “-mllvm” 前缀。典型的 clang 调用如下所示
clang -O2 -mllvm -opt-bisect-limit=256 my_file.c
-opt-bisect-limit 选项也可以应用于链接时优化,方法是使用前缀指示这是链接器的插件选项。以下语法将为 LTO 转换设置二分限制
# When using lld, or ld64 (macOS)
clang -flto -Wl,-mllvm,-opt-bisect-limit=256 my_file.o my_other_file.o
# When using Gold
clang -flto -Wl,-plugin-opt,-opt-bisect-limit=256 my_file.o my_other_file.o
LTO pass 由链接器调用的库实例运行。因此,在主驱动程序编译阶段运行的任何 pass 都不受通过 ‘-Wl,-plugin-opt’ 传递的选项的影响,LTO pass 也不受通过 ‘-mllvm’ 传递给驱动程序调用的 LLVM 调用的选项的影响。
传递 -opt-bisect-print-ir-path=path/foo.ll
将在 -opt-bisect-limit 开始跳过 pass 时将 IR 转储到 path/foo.ll
。
二分索引值¶
与单个索引值关联的优化的粒度是可变的。根据优化 pass 的检测方式,该值可能与优化 pass 在调用它的 IR 单元上执行的所有转换(例如,在 FunctionPass 的 runOnFunction 的单个调用期间)一样多,或者与单个转换一样少。索引值也可能是嵌套的,因此如果未跳过 pass 的调用,则仍可能跳过该调用中的单个转换。
值的分配顺序保证保持稳定和一致,从一次运行到下一次运行,直到并包括指定为限制的值。在限制值之上,跳过优化可能会导致编号发生变化,但由于所有高于限制的优化都被跳过,因此这不是问题。
当 opt-bisect 索引值引用 pass 的 run 函数的整个调用时,pass 将查询是否应跳过它,每次调用都将分配一个唯一值。例如,如果 FunctionPass 与包含三个函数的模块一起使用,则当 pass 运行时,将为每个函数的 pass 分配不同的索引值。pass 可能会在两个函数上运行,但在第三个函数上跳过。
如果 pass 在内部对较小的 IR 单元执行操作,则必须对 pass 进行专门检测,以在此更精细的粒度级别启用二分(有关详细信息,请参见下文)。
使用示例¶
$ opt -O2 -o test-opt.bc -opt-bisect-limit=16 test.ll
BISECT: running pass (1) Simplify the CFG on function (g)
BISECT: running pass (2) SROA on function (g)
BISECT: running pass (3) Early CSE on function (g)
BISECT: running pass (4) Infer set function attributes on module (test.ll)
BISECT: running pass (5) Interprocedural Sparse Conditional Constant Propagation on module (test.ll)
BISECT: running pass (6) Global Variable Optimizer on module (test.ll)
BISECT: running pass (7) Promote Memory to Register on function (g)
BISECT: running pass (8) Dead Argument Elimination on module (test.ll)
BISECT: running pass (9) Combine redundant instructions on function (g)
BISECT: running pass (10) Simplify the CFG on function (g)
BISECT: running pass (11) Remove unused exception handling info on SCC (<<null function>>)
BISECT: running pass (12) Function Integration/Inlining on SCC (<<null function>>)
BISECT: running pass (13) Deduce function attributes on SCC (<<null function>>)
BISECT: running pass (14) Remove unused exception handling info on SCC (f)
BISECT: running pass (15) Function Integration/Inlining on SCC (f)
BISECT: running pass (16) Deduce function attributes on SCC (f)
BISECT: NOT running pass (17) Remove unused exception handling info on SCC (g)
BISECT: NOT running pass (18) Function Integration/Inlining on SCC (g)
BISECT: NOT running pass (19) Deduce function attributes on SCC (g)
BISECT: NOT running pass (20) SROA on function (g)
BISECT: NOT running pass (21) Early CSE on function (g)
BISECT: NOT running pass (22) Speculatively execute instructions if target has divergent branches on function (g)
... etc. ...
Pass 跳过实现¶
-opt-bisect-limit 实现取决于各个 pass 选择加入 opt-bisect 过程。管理该过程的 OptBisect 对象是完全被动的,并且不了解任何 pass 的实现方式。当 pass 运行时,如果可以跳过该 pass,则它应调用 OptBisect 对象以查看是否应跳过它。
OptBisect 对象旨在通过 LLVMContext 访问,并且每个 Pass 基类都包含一个辅助函数,该函数抽象了详细信息,以便使此检查在所有 pass 中保持一致。这些辅助函数是
bool ModulePass::skipModule(Module &M);
bool FunctionPass::skipFunction(const Function &F);
bool LoopPass::skipLoop(const Loop *L);
MachineFunctionPass 应使用 FunctionPass::skipFunction(),如下所示
bool MyMachineFunctionPass::runOnMachineFunction(Function &MF) {
if (skipFunction(*MF.getFunction())
return false;
// Otherwise, run the pass normally.
}
除了使用 OptBisect 类检查是否应跳过 pass 之外,skipFunction()、skipLoop() 和 skipBasicBlock() 辅助函数还会查找 “optnone” 函数属性的存在。调用 pass 将无法确定它是否由于 “optnone” 属性的存在而被跳过,或者是否因为已达到 opt-bisect-limit 而被跳过。这是可取的,因为在任一情况下行为都应相同。
大多数可以跳过的 LLVM pass 已经以如上所述的方式进行了检测。如果您要添加新的 pass,或者认为您找到了一个未包含在 opt-bisect 过程中但应该包含在内的 pass,则可以如上所述添加它。
增加更细粒度¶
一旦确定了执行不正确转换的 pass,执行进一步分析以确定哪个特定转换导致问题可能很有用。调试计数器可以用于此目的。