本节描述如何执行一些非常简单的LLVM代码转换。这意味着要给出常用习惯用法的示例,展示LLVM转换的实用方面。
因为这是一个“how-to”部分,所以您还应该阅读将要使用的主要类。核心LLVM类层次结构参考资料包含您应该了解的主要类的详细信息和描述。
基本检查和遍历例程
LLVM编译器基础架构有许多可以遍历的不同数据结构。以C++标准模板库为例,用于遍历这些不同数据结构的技术基本上是相同的。对于一个可枚举的值序列,XXXbegin()函数(或方法)返回一个序列开头的迭代器,在XXXend()函数返回一个迭代器,该迭代器指向序列最后一个有效元素之后的元素,这两个操作之间有一些XXXiterator数据类型是常见的。
因为迭代的模式在程序表示的许多不同方面都是通用的,所以可以在它们上使用标准模板库算法,并且更容易记住如何迭代。首先,我们展示一些需要遍历的数据结构的常见示例。以非常相似的方式遍历其他数据结构。
遍历一个Function中的BasicBlock
有一个你想要以某种方式转换的函数实例是很常见的;特别是,您希望操作它的基本块。为了实现这一点,您需要遍历构成该Function的所有BasicBlocks。下面是打印一个BasicBlock的名称和它包含的Instructions数的例子:
Function &Func = ... |
遍历一个BasicBlock中的Instruction
就像在函数中处理基本块一样,很容易遍历组成基本块的各个指令。这是一个代码片段,打印出在一个基本块的每个指令:
BasicBlock& BB = ... |
然而,这并不是打印BasicBlock内容的最佳方式!由于ostream操作符实际上重载了您所关心的所有内容,所以您可以调用基本块本身上的打印例程:errs() << BB << "\n";
。
遍历一个Function中的Instruction
如果您发现您通常遍历函数的基本块,然后遍历基本块的指令,那么应该使用InstIterator。您需要include llvm/IR/InstIterator.h,然后在代码中显式实例化InstIterator。下面是一个小例子,展示了如何将函数中的所有指令转储到标准错误流:
#include "llvm/IR/InstIterator.h" |
很容易,不是吗?您还可以使用InstIterator来用工作列表的初始内容填充工作列表。例如,如果你想初始化一个工作列表来包含函数F中的所有指令,你需要做的就是:
std::set<Instruction*> worklist; |
worklist
现在将包含F指向的函数中的所有指令。
将一个 iterator 转换为一个类指针(反之亦然)
有时候,当您手头只有一个iterator时,获取一个类实例的引用(或指针)是很有用的。从 iterator 中提取引用或指针非常简单。假设 i 是一个BasicBlock::iterator,j是一个BasicBlock::const_iterator:
Instruction& inst = *i; // Grab reference to instruction reference |
但是,您将在LLVM框架中使用的 iterator 是特殊的:它们将在需要的时候自动转换为 ptr-to-instance 类型。原本是取消对 iterator 的引用,然后获取结果的地址;代替的是:您可以简单地将 iterator 分配给适当的指针类型,然后您将获得操作的dereference和address作为分配的结果(在幕后,这是重载转换机制的结果)。因此上一个例子的第二行Instruction *pinst = &*i;
,在语义上等价于:Instruction *pinst = i;
也可以将一个类指针转换为相应的 iterator ,这是一个常量时间操作(非常有效)。下面的代码片段演示了使用LLVM iterator 提供的转换构造函数。通过使用这些,您可以显式地获取某个东西的 iterator,而无需通过对某个结构进行迭代来实际获取它:
void printNextInstruction(Instruction* inst) { |
不幸的是,这些隐式转换是有代价的;它们阻止这些 iterator 遵守标准 iterator 约定,从而使它们不能与标准算法和容器一起使用。例如,它们阻止编译下面的代码,其中B是一个BasicBlock:
llvm::SmallVector<llvm::Instruction *, 16>(B->begin(), B->end()); |
因此,这些隐式转换可能会在某一天被删除,并且操作符*将返回指针而不是引用。
查找调用点:一个稍微复杂一点的示例
假设您正在编写一个FunctionPass,并且希望计算整个模块(即跨所有函数)中某个函数(即某个Function*)已经在作用域中的所有位置(被调用的次数)。稍后您将了解到,您可能希望使用 InstVisitor 以一种更直接的方式来实现这一点,但是这个示例将允许我们探索如果没有InstVisitor,您将如何实现这一点。在伪代码中,我们要做的是:
initialize callCounter to zero |
实际的代码是(记住,因为我们在编写FunctionPass,我们的FunctionPass派生类只需要重载runOnFunction方法):
Function* targetFunc = ...; |
以相同的方式处理 calls 和 invokes
您可能已经注意到,前面的示例有些过于简化,因为它没有处理由‘invoke’指令生成的调用站点。在这种情况下,以及在其他情况下,您可能会发现您希望以同样的方式处理 CallInsts 和 InvokeInsts,即使它们最特定的公共基类是 Instruction,其中也包含许多不那么密切相关的东西。对于这些情况,LLVM提供了一个方便的wrapper类 CallSite ,它本质上是一个围绕 Instruction 指针的wrapper,它有一些方法提供 CallInsts 和 InvokeInsts 共有的功能。
该类具有“值语义”:它应该通过值传递,而不是通过引用传递,并且不应该使用 new 或 delete 操作符动态分配或释放该类。它具有高效的可复制性、可分配性和可构造性,其成本相当于一个空指针的成本。如果你看它的定义,它只有一个指针成员。
遍历 def-use 和 use-def 链
通常,我们可能有一个Value类的实例,我们希望确定哪些Users使用这个值。具有特定Value的所有Users的列表称为def-use链。例如,我们有一个 Function* F 指向一个特定的函数 foo。找到所有使用 foo 的指令就像遍历 F 的def-use链一样简单:
Function *F = ...; |
或者,通常有一个User类的实例,并且需要知道它使用什么Values。一个User使用的所有Values的列表称为use-def链。类Instruction的实例是常见的User,所以我们可能需要遍历特定Instruction使用的所有values(即特定Instruction的操作数):
Instruction *pi = ...; |
将对象声明为 const 是实现无变化算法(如分析等)的一个重要工具。为此,上面的iterators有两种固定的风格:Value::const_use_iterator和Value::const_op_iterator。当分别调用 const Values 或 const Users 上的use/op_begin()时,它们会自动出现。在取消引用后,它们返回const Use*s。否则,上述模式将保持不变。
遍历块的前置和后继
使用“llvm/IR/CFG.h”中定义的例程,遍历块的前置和后继是非常容易的。只需使用这样的代码来遍历所有BB的前置:
#include "llvm/IR/CFG.h" |
类似地,要遍历后继,可以使用successors