动态加载的Pass

在使用LLVM构建生产质量工具时,大小很重要,既用于分发,也用于在目标系统上运行时调整驻留代码大小。因此,期望选择性地使用一些通道,同时省略其他通道并保持稍后改变配置的灵活性。您希望能够完成所有这些,并向用户提供反馈。这是Pass注册发挥作用的地方。

传递注册的基本机制是MachinePassRegistry类和MachinePassRegistryNode类的子类。

MachinePassRegistry的实例用于维护MachinePassRegistryNode对象列表 。此实例维护列表并向命令行界面传达添加和删除。

MachinePassRegistryNode子类的实例用于维护有关特定传递的信息。此信息包括命令行名称,命令帮助字符串以及用于创建传递实例的函数的地址。这些实例之一的全球静态构造函数注册了相应的MachinePassRegistry,静态的析构函数注销。因此,在工具中静态链接的传递将在启动时注册。动态加载的传递将在加载时注册,并在卸载时注销。

使用现有的注册表

有预定义的注册表来跟踪指令调度(RegisterScheduler)和寄存器分配(RegisterRegAlloc)机器传递。这里我们将描述如何注册寄存器分配器机器Pass。

实现你的寄存器分配器机器通行证。在您的register allocator .cpp文件中添加以下内容:

#include "llvm/CodeGen/RegAllocRegistry.h"

同样在您的注册器分配器.cpp文件中,以下列形式定义创建者函数:

FunctionPass *createMyRegisterAllocator() {
return new MyRegisterAllocator();
}

请注意,此函数的签名应与RegisterRegAlloc::FunctionPassCtor类型匹配。在同一个文件中添加“安装”声明,格式如下:

static RegisterRegAlloc myRegAlloc("myregalloc",
"my register allocator help string",
createMyRegisterAllocator);

请注意,帮助字符串之前的两个空格会在-help查询上产生整洁的结果 。

$ llc -help
...
-regalloc - Register allocator to use (default=linearscan)
=linearscan - linear scan register allocator
=local - local register allocator
=simple - simple register allocator
=myregalloc - my register allocator help string
...

就是这样。用户现在可以自由选择使用-regalloc=myregalloc。除了使用RegisterScheduler类之外,注册指令调度程序是类似的 。请注意,与… RegisterScheduler::FunctionPassCtor有显着差异 RegisterRegAlloc::FunctionPassCtor。

要强制将寄存器分配器加载/链接到 llc / lli工具中,请将创建者函数的全局声明Passes.h添加到并添加“伪”调用行 llvm/Codegen/LinkAllCodegenComponents.h。

创建新的注册表

最简单的入门方法是克隆一个现有的注册表; 我们建议llvm/CodeGen/RegAllocRegistry.h。要修改的关键是类名和FunctionPassCtor类型。

然后你需要声明注册表。示例:如果您的pass注册表是 RegisterMyPasses定义的:

MachinePassRegistry RegisterMyPasses::Registry;

最后,为你的传递声明命令行选项。例:

cl::opt<RegisterMyPasses::FunctionPassCtor, false,
RegisterPassParser<RegisterMyPasses> >
MyPassOpt("mypass",
cl::init(&createDefaultMyPass),
cl::desc("my pass option help"));

这里的命令选项是“ mypass”,createDefaultMyPass默认创建者

在GDB中使用动态加载的Pass

不幸的是,使用GDB和动态加载的传递并不像应该的那样容易。首先,您不能在尚未加载的共享对象中设置断点,其次在共享对象中存在内联函数的问题。以下是使用GDB调试传递的一些建议。

为了便于讨论,我将假设您正在调试由opt调用的转换,尽管此处描述的内容不依赖于此。

在Pass中设置断点

你要做的第一件事是在opt过程中启动gdb:

$ gdb opt
GNU gdb 5.0
Copyright 2000 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "sparc-sun-solaris2.6"...
(gdb)

请注意,opt中包含大量调试信息,因此加载需要时间。耐心点。由于我们尚未在传递中设置断点(共享对象直到运行时才加载),我们必须执行该过程,并在它调用我们的传递之前停止它,但是在它加载了共享对象之后。最简单的方法是设置断点 PassManager::run,然后使用您想要的参数运行该过程:

$ (gdb) break llvm::PassManager::run
Breakpoint 1 at 0x2413bc: file Pass.cpp, line 70.
(gdb) run test.bc -load $(LLVMTOP)/llvm/Debug+Asserts/lib/[libname].so -[passoption]
Starting program: opt test.bc -load $(LLVMTOP)/llvm/Debug+Asserts/lib/[libname].so -[passoption]
Breakpoint 1, PassManager::run (this=0xffbef174, [email protected]) at Pass.cpp:70
70 bool PassManager::run(Module &M) { return PM->run(M); }
(gdb)

一旦opt停止在PassManager::run方法中,您现在可以在通道中自由设置断点,以便您可以跟踪执行或执行其他标准调试。

杂项问题

一旦掌握了基础知识,GDB就会遇到一些问题,一些有解决方案,一些没有解决方案。

  • 内联函数具有伪造的堆栈信息。通常,GDB在获取堆栈跟踪和单步执行内联函数方面做得非常好。但是,当动态加载传递时,它会以某种方式完全失去此功能。我所知道的唯一解决方案是对函数进行去内联(将其从类的主体移动到.cpp文件中)。
  • 重新启动程序会破坏断点。按照上述信息后,您已成功获得通行证中的一些断点。接下来你知道,你重新启动程序(即你run再次输入“ ”),然后开始得到关于断点无法设置的错误。我发现“修复”此问题的唯一方法是删除已在传递中设置的断点,运行程序,并在执行停止后重新设置断点PassManager::run。