使用 -opt-bisect-limit 调试优化错误

简介

-opt-bisect-limit 选项提供了一种方法,可以在不修改 Pass Manager 填充方式的情况下禁用指定限制以上的所有优化 Pass。此选项的目的是帮助跟踪优化过程中错误的转换导致运行时行为错误的问题。

此功能以选择加入的方式实现。可以在仍然允许正确代码生成的情况下安全跳过的 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 的检测方式,该值可能与针对其调用(例如,在 FunctionPass 的单个 runOnFunction 调用期间)的 IR 单元的优化 Pass 将执行的所有转换一样多,或者与单个转换一样少。索引值也可能嵌套,因此,如果 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 CallGraphSCCPass::skipSCC(CallGraphSCC &SCC);
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,执行进一步分析以确定导致问题的具体转换可能很有用。调试计数器可用于此目的。