LLVM开发者手册
- 1. 简介
- 2. 通用信息
- 3. 重要且有用的LLVM APIs
- 3.1 isa<>、cast<> 和 dyn_cast<> 模板
- 3.2 传递字符串(StringRef和Twine类)
- 3.3 格式化字符串(formatv函数)
- 3.4 错误处理
- 3.5 传递函数和其他可调用对象
- 3.6 LLVM_DEBUG()宏和-debug选项
- 3.7 Statistic类& -stats选项
- 3.8 添加调试计数器来帮助调试代码
- 3.9 调试代码时查看图形
- 4. 为任务选择正确的数据结构
- 4.1 Sequential Containers (std::vector, std::list, etc)
- 4.1.1 lvm/ADT/ArrayRef.h
- 4.1.2 Fixed Size Arrays
- 4.1.3 Heap Allocated Arrays
- 4.1.4 llvm/ADT/TinyPtrVector.h
- 4.1.5 llvm/ADT/SmallVector.h
- 4.1.6
- 4.1.7
- 4.1.8
- 4.1.9 llvm/ADT/ilist.h
- 4.1.10 llvm/ADT/PackedVector.h
- 4.1.11 ilist_traits
- 4.1.12 iplist
- 4.1.13 llvm/ADT/ilist_node.h
- 4.1.14 Sentinels
- 4.1.15 Other Sequential Container options
- 4.2 String-like containers
- 4.3 Set-Like Containers (std::set, SmallSet, SetVector, etc)
- 4.3.1 A sorted ‘vector’
- 4.3.2 llvm/ADT/SmallSet.h
- 4.3.3 llvm/ADT/SmallPtrSet.h
- 4.3.4 llvm/ADT/StringSet.h
- 4.3.5 llvm/ADT/DenseSet.h
- 4.3.6 llvm/ADT/SparseSet.h
- 4.3.7 llvm/ADT/SparseMultiSet.h
- 4.3.8 llvm/ADT/FoldingSet.h
- 4.3.9
- 4.3.10 llvm/ADT/SetVector.h
- 4.3.11 llvm/ADT/UniqueVector.h
- 4.3.12 llvm/ADT/ImmutableSet.h
- 4.3.13 Other Set-Like Container Options
- 4.4 Map-Like Containers (std::map, DenseMap, etc)
- 4.4.1 A sorted ‘vector’
- 4.4.2 llvm/ADT/StringMap.h
- 4.4.3 llvm/ADT/IndexedMap.h
- 4.4.4 llvm/ADT/DenseMap.h
- 4.4.5 llvm/IR/ValueMap.h
- 4.4.6 llvm/ADT/IntervalMap.h
- 4.4.7
- 4.4.8 llvm/ADT/MapVector.h
- 4.4.9 llvm/ADT/IntEqClasses.h
- 4.4.10 llvm/ADT/ImmutableMap.h
- 4.4.11 Other Map-Like Container Options
- 4.5 Bit storage containers (BitVector, SparseBitVector)
- 5. 调试
- 6. 常见操作的有用提示
- 6.1 基本检查和遍历例程
- 6.1.1 遍历一个Function中的BasicBlock
- 6.1.2 遍历一个BasicBlock中的Instruction
- 6.1.3 遍历一个Function中的Instruction
- 6.1.4 将一个 iterator 转换为一个类指针(反之亦然)
- 6.1.5 查找调用站点:一个稍微复杂一点的示例
- 6.1.6 以相同的方式处理 calls 和 invokes
- 6.1.7 遍历 def-use 和 use-def 链
- 6.1.8 遍历块的前置和后继
- 6.2 做些简单的改变
- 7. 线程和LLVM
- 8. 高级的主题
- 9. 核心LLVM类层次结构参考资料
说明:本文为译文,点击 此处查看原文。
1. 简介
本文档旨在突出显示LLVM源代码库中可用的一些重要类和接口。本手册不打算解释什么是LLVM,它是如何工作的,以及LLVM代码是什么样子的。它假设您了解LLVM的基本知识,并且对编写转换或其他分析或操作代码感兴趣。
这个文档应该使您面向对象,这样您就可以在组成LLVM基础架构的不断增长的源代码中找到自己的方法。请注意,本手册不打算替代阅读源代码,所以如果您认为这些类中应该有一个方法来做某些事情,但是没有列出,请检查源代码。本文提供了到doxygen源的链接,使这尽可能容易理解。
本文档的第一部分描述了在LLVM基础架构中工作时需要了解的一般信息,第二部分描述了核心LLVM类。在未来,本手册将扩展描述如何使用扩展库的信息,如dominator信息、CFG遍历例程和InstVisitor (doxygen)模板等有用的实用程序。
2. 通用信息
本节包含一些通用信息,如果您在LLVM源代码库中工作,这些信息非常有用,但是这些信息并不特定于任何特定的API。
2.1 C++标准模板库
LLVM大量使用了C++标准模板库(STL),这可能比您以前使用或见过的要多得多。因此,您可能想要对库中使用的技术和功能做一些背景阅读。有很多讨论STL的不错的页面,您可以找到一些关于这个主题的书籍,因此在本文中不会讨论它。
以下是一些有用的链接:
- cppreference.com —— 对于STL和标准C++库的其他部分是一个很好的参考。
- C++ In a Nutshell —— 简而言之,这是一本正在制作中的O’Reilly的书。它有一个相当不错的标准库参考资料,可以和Dinkumware的媲美,但不幸的是,自从这本书出版以来,它就不再是免费的了。
- C++常见问题。
- SGI’s STL程序员指南 —— 包含一个有用的STL介绍。
- Bjarne Stroustrup’s C++ Page。
- Bruce Eckel的《Thinking in C++》,第二版,第二卷,修订版4.0。
还鼓励您阅读LLVM编码标准指南,该指南侧重于如何编写可维护的代码,而不是简单的介绍花括号放在哪里。
2.2 其他有用的参考资料
3. 重要且有用的LLVM APIs
在这里,我们重点介绍了一些LLVM APIs,它们通常很有用,在编写转换时很容易了解它们。
3.1 isa<>、cast<> 和 dyn_cast<> 模板
LLVM源代码库广泛使用了RTTI的自定义形式。这些模板与c++ dynamic_cast<>操作符有很多相似之处,但是它们没有一些缺点(主要是因为 dynamic_cast<> 只适用于具有一个v-table的类)。因为它们经常被使用,所以您必须知道它们用来做什么以及如何工作。所有这些模板都是在 llvm/Support/Casting.h(doxygen) 文件中定义的(注意,很少需要直接包含该文件)。
- isa<>:
isa<>
操作符的工作原理与Java “instanceof”操作符完全相同。它返回true或false,这取决于引用或指针是否指向指定类的实例。这对于各种类型的约束检查非常有用(下面的示例)。 - cast<>:
cast<>
操作符是一个“已检查的强制转换”操作。它将指针或引用从基类转换为派生类,如果不是正确类型的实例,则会导致断言失败。当你有一些信息使你相信某样东西是正确的类型时,应该使用这种方法。isa<> 和 cast<> 模板的一个例子是:
注意,您不应该使用 isa<> 测试后面紧跟着一个 cast<>,因为它使用 dyn_cast<> 操作符。static bool isLoopInvariant(const Value *V, const Loop *L) { if (isa<Constant>(V) || isa<Argument>(V) || isa<GlobalValue>(V)) return true; // Otherwise, it must be an instruction... return !L->contains(cast<Instruction>(V)->getParent()); }
- dyn_cast<>:
dyn_cast<>
操作符是一个“检查转换”操作。它检查操作数是否属于指定的类型,如果是,则返回指向它的指针(该操作符不与引用一起工作)。如果操作数类型不正确,则返回空指针。因此,这与c++中的dynamic_cast<>操作符非常相似,应该在相同的环境中使用。通常,dyn_cast<>操作符用于if语句或其他类似的流控制语句中:
这种形式的if语句有效地将对 isa<> 的调用和对 cast<> 的调用组合到一个语句中,这非常方便。if (auto *AI = dyn_cast<AllocationInst>(Val)) { // ... }
注意,dyn_cast<>操作符可以被滥用,就像c++的dynamic_cast<>或Java的instanceof操作符一样。特别是,不应该使用大的if/then/else块来检查类的许多不同变体。如果您发现自己想这样做,那么使用 InstVisitor 类直接分派指令类型会更清晰、更有效。 - cast_or_null<>:
cast_or_null<>
操作符的工作原理与 cast<>操作符类似,只是它允许一个空指针作为参数(然后将其传播)。这有时很有用,允许您将多个null检查合并到一个检查中。 - dyn_cast_or_null<>:
dyn_cast_or_null<>
操作符的工作原理与 dyn_cast<> 操作符类似,只是它允许一个空指针作为参数(然后将其传播)。这有时很有用,允许您将多个null检查合并到一个检查中。
这五个模板可以用于任何类,无论它们是否具有一个v-table。如果希望添加对这些模板的支持,请参阅如何为类层次结构设置LLVM样式的RTTI的文档
3.2 传递字符串(StringRef和Twine类)
虽然LLVM通常不做太多字符串操作,但是我们有几个重要的APIs接受字符串。两个重要的例子是 Value
类(它有指令、函数等的名称)和 StringMap
类(在 LLVM 和 Clang 中广泛使用)。
这些是泛型类,它们需要能够接受可能包含空字符的字符串。因此,它们不能简单地接受const char *
,而接受const std::string&
要求客户机执行堆分配,这通常是不必要的。代替的是,许多LLVM APIs使用StringRef
或const twine&
来有效地传递字符串。
3.2.1 StringRef类
StringRef
数据类型表示对常量字符串(一个字符数组和一个长度)的引用,并支持std::string
上可用的公共操作,但不需要堆分配。
它可以使用一个C风格的以null结尾的字符串、一个std::string
隐式地被造,也可以使用一个字符指针和长度显式地构造。例如,StringRef find
函数声明为:
iterator find(StringRef Key);
client可以用以下任意一种方式调用这个函数:
Map.find("foo"); // Lookup "foo"
Map.find(std::string("bar")); // Lookup "bar"
Map.find(StringRef("\0baz", 4)); // Lookup "\0baz"
类似地,需要返回string
的APIs可能会返回一个StringRef
实例,该实例可以直接使用,也可以使用str
成员函数将其转换为std::string
。有关更多信息,请查看 llvm/ADT/StringRef.h (doxygen)。
您应该很少直接使用StringRef
类,因为它包含指向外部内存的指针,所以存储该类的实例通常是不安全的(除非您知道不会释放外部存储)。StringRef
在 LLVM 中足够小和普遍,因此它应该总是通过值传递。
3.2.2 Twine类
Twine
(doxygen)类是 APIs 接受连接字符串的有效方法。例如,一个常见的LLVM范型是根据带有后缀的另一条指令的名称来命名一条指令,例如:
New = CmpInst::Create(..., SO->getName() + ".cmp");
Twine
类实际上是一个轻量级的rope,它指向临时(分配给栈的)对象。Twine
可以隐式地构造为加运算符应用于字符串的结果(即,一个C字符串,一个std::string
,或者一个StringRef
)。Twine
会延迟字符串的实际连接,直到实际需要它时,才会有效地将其直接呈现到字符数组中。这避免了在构造字符串连接的临时结果时涉及的不必要的堆分配。有关更多信息,请查看 llvm/ADT/Twine.h(doxygen)和这里。
与StringRef
一样,Twine
对象指向外部内存,并且几乎不应该直接存储或提及。它们仅用于在定义一个应该能够有效接受连接字符串的函数时使用。
3.3 格式化字符串(formatv函数)
虽然LLVM不一定要做很多字符串操作和解析,但它确实做了很多字符串格式化。从诊断消息,到llvm工具输出(如llvm-readobj
),再到打印详细的分解清单和LLDB运行时日志,字符串格式化的需求无处不在。
formatv
在本质上类似于printf
,但是使用了另一种语法,这种语法大量借鉴了Python和c#。与printf不同,它推断要在编译时格式化的类型,因此不需要%d之类的格式说明符。这减少了构造可移植格式字符串的脑力开销,特别是对于size_t或指针类型等特定于平台的类型。与printf和Python不同的是,如果LLVM不知道如何格式化类型,它还不能编译。这两个属性确保函数比传统的格式化方法(如printf函数族)更安全,使用起来也更简单。
3.3.1 简单的格式化
formatv
调用涉及一个由0个或多个替换序列
组成的格式字符串,然后是替换值
的一个可变长度列表。一个替换序列是一个形式为{N[[,align]:style]}
的字符串。
N表示替换值列表中参数的基于0的索引。注意,这意味着可以以任何顺序多次引用相同的参数,可能使用不同的样式和/或对齐选项。
align是一个可选字符串,指定要将值格式化为的字段的宽度,以及字段内值的对齐方式。它被指定为一个可选的对齐样式
,后跟一个正整数字段宽度
。对齐样式可以是字符-(左对齐)、=(中对齐)或+(右对齐)
中的一个。默认值是右对齐的。
style是一个可选字符串,由控制值格式的特定类型组成。例如,要将浮点值格式化为百分比,可以使用样式选项P。
3.3.2 自定义格式化
有两种方法可以定制一个类型的格式化行为。
- 使用适当的静态格式化方法为您的类型T提供
llvm::format_provider<T>
的模板专门化。
这是一个有用的扩展机制,用于添加对使用自定义样式选项格式化自定义类型的支持。但是,当您想要扩展格式化库已经知道如何格式化的类型的机制时,它没有帮助。为此,我们需要别的东西。namespace llvm { template<> struct format_provider<MyFooBar> { static void format(const MyFooBar &V, raw_ostream &Stream, StringRef Style) { // Do whatever is necessary to format `V` into `Stream` } }; void foo() { MyFooBar X; std::string S = formatv("{0}", X); } }
- 提供从llvm::FormatAdapter继承的格式适配器。
如果检测到该类型派生自namespace anything { struct format_int_custom : public llvm::FormatAdapter<int> { explicit format_int_custom(int N) : llvm::FormatAdapter<int>(N) {} void format(llvm::raw_ostream &Stream, StringRef Style) override { // Do whatever is necessary to format ``this->Item`` into ``Stream`` } }; } namespace llvm { void foo() { std::string S = formatv("{0}", anything::format_int_custom(42)); } }
FormatAdapter<T>
,formatv
将对以指定样式传递的参数调用format
方法。这允许提供任何类型的自定义格式,包括已经有内置格式提供程序的格式。
3.3.3 formatv例子
下面将提供一组不完整的示例,演示formatv
的用法。通过阅读doxygen文档或查看单元测试套件可以找到更多信息。
std::string S;
// 基本类型的简单格式化和隐式字符串转换。
S = formatv("{0} ({1:P})", 7, 0.35); // S == "7 (35.00%)"
// 无序引用和多引用
outs() << formatv("{0} {2} {1} {0}", 1, "test", 3); // prints "1 3 test 1"
// 左、右、中对齐
S = formatv("{0,7}", 'a'); // S == " a";
S = formatv("{0,-7}", 'a'); // S == "a ";
S = formatv("{0,=7}", 'a'); // S == " a ";
S = formatv("{0,+7}", 'a'); // S == " a";
// 自定义样式
S = formatv("{0:N} - {0:x} - {1:E}", 12345, 123908342); // S == "12,345 - 0x3039 - 1.24E8"
// Adapters
S = formatv("{0}", fmt_align(42, AlignStyle::Center, 7)); // S == " 42 "
S = formatv("{0}", fmt_repeat("hi", 3)); // S == "hihihi"
S = formatv("{0}", fmt_pad("hi", 2, 6)); // S == " hi "
// Ranges
std::vector<int> V = {8, 9, 10};
S = formatv("{0}", make_range(V.begin(), V.end())); // S == "8, 9, 10"
S = formatv("{0:$[+]}", make_range(V.begin(), V.end())); // S == "8+9+10"
S = formatv("{0:$[ + ]@[x]}", make_range(V.begin(), V.end())); // S == "0x8 + 0x9 + 0xA"
3.4 错误处理
正确的错误处理帮助我们识别代码中的错误,并帮助最终用户理解他们的工具使用中的错误。错误可以分为两大类:编程错误和可恢复性错误,它们具有不同的处理和报告策略。
3.4.1 编程错误
编程错误是对程序不变量或API契约的违反,并表示程序本身中的错误。我们的目标是记录不变量,并在不变量在运行时被破坏时在故障点快速中止(提供一些基本的诊断)。
处理编程错误的基本工具是断言
和llvm_unaccessible
函数。断言用于表示不变条件,并且应该包含描述不变条件的消息:
assert(isPhysReg(R) && "All virt regs should have been allocated already.");
llvm_unaccessible
函数可用于记录控制流的区域,如果程序不变量保持:
enum { Foo, Bar, Baz } X = foo();
switch (X) {
case Foo: /* Handle Foo */; break;
case Bar: /* Handle Bar */; break;
default:
llvm_unreachable("X should be Foo or Bar here");
}
3.4.2 可恢复性错误
可恢复错误表示程序环境中的错误,例如资源故障(丢失的文件、丢失的网络连接等)或格式错误的输入。应该检测这些错误,并将其传达给程序的某个级别,以便对其进行适当处理。处理错误可能与向用户报告问题一样简单,也可能涉及尝试恢复。
请注意
虽然在整个LLVM中使用这种错误处理方案是理想的,但是在某些地方这种方法并不实用。在绝对必须发出非编程错误且错误模型不可用的情况下,可以调用report_fatal_error,它将调用已安装的错误处理程序、打印一条消息并退出程序。
可恢复错误使用LLVM的错误模式建模。这个方案使用函数返回值表示错误,类似于经典的C整数错误代码,或者c++的std::error_code。然而,Error类实际上是用户定义错误类型的轻量级包装器,允许附加任意信息来描述错误。这类似于c++异常允许抛出用户定义类型的方式。
成功值是通过调用Error:: Success()创建的,例如:
Error foo() {
// Do something.
// Return success.
return Error::success();
}
成功值的构建和返回非常便宜——它们对程序性能的影响很小。
使用make_error构造失败值,其中T是继承自ErrorInfo实用程序的任何类,例如:
class BadFileFormat : public ErrorInfo<BadFileFormat> {
public:
static char ID;
std::string Path;
BadFileFormat(StringRef Path) : Path(Path.str()) {}
void log(raw_ostream &OS) const override {
OS << Path << " is malformed";
}
std::error_code convertToErrorCode() const override {
return make_error_code(object_error::parse_failed);
}
};
char BadFileFormat::ID; // This should be declared in the C++ file.
Error printFormattedFile(StringRef Path) {
if (<check for valid format>)
return make_error<BadFileFormat>(Path);
// print file contents.
return Error::success();
}
错误值可以隐式地转换为bool: true for Error, false for success
,启用以下习语:
Error mayFail();
Error foo() {
if (auto Err = mayFail())
return Err;
// Success! We can proceed.
...
对于可能失败但需要返回值的函数,可以使用预期的实用程序。这种类型的值可以用T或错误构造。预期值也可以隐式转换为布尔值,但与错误的约定相反:true表示成功,false表示错误。如果成功,可以通过取消引用操作符访问T值。如果失败,可以使用takeError()方法提取错误值。习惯用法如下:
Expected<FormattedFile> openFormattedFile(StringRef Path) {
// If badly formatted, return an error.
if (auto Err = checkFormat(Path))
return std::move(Err);
// Otherwise return a FormattedFile instance.
return FormattedFile(Path);
}
Error processFormattedFile(StringRef Path) {
// Try to open a formatted file
if (auto FileOrErr = openFormattedFile(Path)) {
// On success, grab a reference to the file and continue.
auto &File = *FileOrErr;
...
} else
// On error, extract the Error value and return it.
return FileOrErr.takeError();
}
如果一个Expected值处于成功模式,则takeError()方法将返回一个成功值。利用这一事实,可以将上述函数改写为:
Error processFormattedFile(StringRef Path) {
// Try to open a formatted file
auto FileOrErr = openFormattedFile(Path);
if (auto Err = FileOrErr.takeError())
// On error, extract the Error value and return it.
return Err;
// On success, grab a reference to the file and continue.
auto &File = *FileOrErr;
...
}
对于包含多个预期值的函数,第二种形式通常更具可读性,因为它限制了所需的缩进。
所有错误实例,无论是成功还是失败,都必须在销毁之前进行检查或从(通过std::move或return)移动(通过std::move或return)。意外丢弃未检查的错误将导致程序在未检查值的析构函数运行时中止,从而很容易识别和修复违反此规则的行为。
一旦测试成功值(通过调用布尔转换操作符),就认为它们已被检查:
if (auto Err = mayFail(...))
return Err; // Failure value - move error to caller.
// Safe to continue: Err was checked.
相反,下面的代码总是会导致中止,即使mayFail返回一个成功值:
mayFail();
// Program will always abort here, even if mayFail() returns Success, since
// the value is not checked.
一旦错误类型的处理程序被激活,就会认为检查了故障值:
handleErrors(
processFormattedFile(...),
[](const BadFileFormat &BFF) {
report("Unable to process " + BFF.Path + ": bad format");
},
[](const FileNotFound &FNF) {
report("File not found " + FNF.Path);
});
handleErrors函数将一个错误作为它的第一个参数,然后是一个由“处理程序”组成的可变参数列表,其中每个处理程序必须是一个可调用类型(函数、lambda或带有调用操作符的类)和一个参数。handleErrors函数将访问序列中的每个处理程序,并根据错误的动态类型检查其参数类型,运行第一个匹配的处理程序。这与决定为c++异常运行哪个catch子句所用的决策过程相同。
由于传递给handleErrors的处理程序列表可能不能覆盖所有可能发生的错误类型,因此handleErrors函数还返回一个必须检查或传播的错误值。如果传递给handleErrors的错误值与任何处理程序不匹配,则将从handleErrors返回该值。因此,handleErrors的习惯用法如下:
if (auto Err =
handleErrors(
processFormattedFile(...),
[](const BadFileFormat &BFF) {
report("Unable to process " + BFF.Path + ": bad format");
},
[](const FileNotFound &FNF) {
report("File not found " + FNF.Path);
}))
return Err;
在您真正知道处理程序列表是详尽的情况下,可以使用handleAllErrors函数。这与handleErrors相同,只是如果传入未处理的错误,它将终止程序,因此可以返回void。通常应该避免handleAllErrors函数:在程序的其他地方引入新的错误类型可以很容易地将以前详尽的错误列表转换为非详尽的列表,从而冒着程序意外终止的风险。在可能的情况下,使用handleErrors并将未知的错误传播到堆栈中。
对于工具代码,可以通过打印错误消息然后使用错误代码退出来处理错误,ExitOnError实用程序可能是比handleErrors更好的选择,因为它简化了调用易出错函数时的控制流。
在已知的情况下,一个特定的调用的函数总是成功(例如,调用一个函数,它只能失败的一个子集的输入和输入,是安全的)cantFail函数可以用来删除错误类型,简化控制流。
3.4.2.1 StringError
许多类型的错误没有恢复策略,惟一可以采取的操作是将它们报告给用户,以便用户可以尝试修复环境。在本例中,将错误表示为字符串非常有意义。LLVM为此提供了StringError类。它接受两个参数:字符串错误消息和用于互操作性的等效std::error_code。它还提供了一个createStringError函数来简化这个类的常见用法:
// These two lines of code are equivalent:
make_error<StringError>("Bad executable", errc::executable_format_error);
createStringError(errc::executable_format_error, "Bad executable");
如果您确定您正在构建的错误永远不需要转换为std::error_code,那么您可以使用inconvertibleErrorCode()函数:
createStringError(inconvertibleErrorCode(), "Bad executable");
只有在仔细考虑之后才能这样做。如果试图将此错误转换为std::error_code,则会立即触发程序终止。除非您确定您的错误不需要互操作性,否则您应该寻找一个现有的std::error_code,您可以将其转换为它,甚至(尽管这很痛苦)考虑引入一个新的std::error_code作为权宜之计。
createStringError可以使用printf样式的格式说明符来提供格式化的消息:
createStringError(errc::executable_format_error, "Bad executable: %s", FileName);
3.4.2.2 与std::error_code和ErrorOr的互操作性
许多现有的LLVM api都使用std::error_code及其合作伙伴ErrorOr(它的作用与Expected相同,但是包装的是std::error_code,而不是错误)。错误类型的传染性意味着,试图更改其中一个函数以返回Error或Expected,结果往往导致对调用者、调用者的调用者的大量更改,等等。(第一次尝试,从MachOObjectFile的构造函数返回一个错误,在diff达到3000行之后被放弃,影响了6个库,并且仍然在增长)。
为了解决这个问题,引入了Error/std::error_code互操作性需求。两对函数允许任何错误值转换为std::error_code,任何期望的转换为ErrorOr,反之亦然:
std::error_code errorToErrorCode(Error Err);
Error errorCodeToError(std::error_code EC);
template <typename T> ErrorOr<T> expectedToErrorOr(Expected<T> TOrErr);
template <typename T> Expected<T> errorOrToExpected(ErrorOr<T> TOrEC);
使用这些api,可以很容易地制作外科补丁,将单个函数从std::error_code更新为Error,并将ErrorOr更新为预期的。
3.4.2.3 从错误处理程序返回错误
错误恢复尝试本身可能会失败。因此,handleErrors实际上可以识别三种不同形式的处理程序签名:
// Error must be handled, no new errors produced:
void(UserDefinedError &E);
// Error must be handled, new errors can be produced:
Error(UserDefinedError &E);
// Original error can be inspected, then re-wrapped and returned (or a new
// error can be produced):
Error(std::unique_ptr<UserDefinedError> E);
从处理程序返回的任何错误都将从handleErrors函数返回,以便它可以自己处理,或者向上传播堆栈。
3.4.2.4 使用ExitOnError简化工具代码
库代码不应该为可恢复错误调用exit,但是在工具代码中(尤其是命令行工具),这是一种合理的方法。遇到错误时调用exit可以极大地简化控制流,因为不再需要将错误传播到堆栈上。这允许以直线方式编写代码,只要每个容易出错的调用都封装在check中并调用退出即可。ExitOnError类支持这种模式,它提供检查错误值的调用操作符,在成功的情况下清除错误,并将日志记录到stderr,然后在失败的情况下退出。
要使用这个类,请在程序中声明一个全局ExitOnError变量:
ExitOnError ExitOnErr;
对易出错函数的调用可以用对ExitOnErr的调用进行包装,将它们转换为非失败调用:
Error mayFail();
Expected<int> mayFail2();
void foo() {
ExitOnErr(mayFail());
int X = ExitOnErr(mayFail2());
}
失败时,错误的日志消息将被写入stderr,前面可选地加上一个字符串“banner”,可以通过调用setBanner方法设置该字符串。还可以使用setExitCodeMapper方法从错误值映射到退出代码:
int main(int argc, char *argv[]) {
ExitOnErr.setBanner(std::string(argv[0]) + " error:");
ExitOnErr.setExitCodeMapper(
[](const Error &Err) {
if (Err.isA<BadFileFormat>())
return 2;
return 1;
});
在您的工具代码中尽可能使用ExitOnError,因为它可以极大地提高可读性。
3.4.2.5 使用cantFail可以简化安全的调用站点
有些函数可能只对其输入的子集失败,因此可以假定使用已知安全输入的调用成功。
cantFail函数封装了这一点,它封装了一个断言,即它们的参数是一个成功值,并且,在预期的情况下,解包T值:
Error onlyFailsForSomeXValues(int X);
Expected<int> onlyFailsForSomeXValues2(int X);
void foo() {
cantFail(onlyFailsForSomeXValues(KnownSafeValue));
int Y = cantFail(onlyFailsForSomeXValues2(KnownSafeValue));
...
}
与ExitOnError实用程序一样,cantFail简化了控制流。但是,它们对错误情况的处理非常不同:当ExitOnError保证在错误输入时终止程序时,cantFile简单地断言结果是成功的。在调试构建中,如果遇到错误,这将导致断言失败。在release构建中,cantFail的行为没有定义失败值。因此,在使用cantFail时必须非常小心:客户端必须确保cantFail包装的调用确实不能因为给定的参数而失败。
cantFail函数在库代码中应该很少使用,但是它们更可能用于工具和单元测试代码中,在这些代码中,输入和/或模拟的类或函数可能是安全的。
3.4.2.6 的构造函数
有些类需要资源获取或其他复杂的初始化,在构建过程中可能会失败。不幸的是,构造函数不能返回错误,而在构造完客户端测试对象以确保它们是有效的之后,让客户端测试对象很容易出错,因为很容易忘记测试。要解决这个问题,使用命名构造函数习惯用法并返回一个期望的:
class Foo {
public:
static Expected<Foo> Create(Resource R1, Resource R2) {
Error Err;
Foo F(R1, R2, Err);
if (Err)
return std::move(Err);
return std::move(F);
}
private:
Foo(Resource R1, Resource R2, Error &Err) {
ErrorAsOutParameter EAO(&Err);
if (auto Err2 = R1.acquire()) {
Err = std::move(Err2);
return;
}
Err = R2.acquire();
}
};
在这里,指定的构造函数通过引用将错误传递给实际的构造函数,然后构造函数可以使用该构造函数返回错误。ErrorAsOutParameter实用程序在进入构造函数时设置错误值的checked标志,以便将错误分配给构造函数,然后在退出时重置该标志,以强制客户机(指定的构造函数)检查错误。
通过使用这个习惯用法,试图构造Foo的客户端要么接收格式良好的Foo,要么接收错误,而不是处于无效状态的对象。
3.4.2.7 根据类型传播和消耗错误
在某些上下文中,某些类型的错误被认为是良性的。例如,在遍历归档文件时,一些客户机可能乐于跳过格式糟糕的目标文件,而不是立即终止遍历。可以使用一种精心设计的处理程序方法来跳过格式糟糕的对象,但是Error.h头提供了两个实用程序,使这个习惯用法更加简洁:类型检查方法isA和consumeError函数:
Error walkArchive(Archive A) {
for (unsigned I = 0; I != A.numMembers(); ++I) {
auto ChildOrErr = A.getMember(I);
if (auto Err = ChildOrErr.takeError()) {
if (Err.isA<BadFileFormat>())
consumeError(std::move(Err))
else
return Err;
}
auto &Child = *ChildOrErr;
// Use Child
...
}
return Error::success();
}
``
###### 3.4.2.8 连接错误和joinErrors
在上面的归档行走示例中,BadFileFormat错误被简单地使用和忽略。如果客户想在完成归档后报告这些错误,他们可以使用joinErrors实用工具:
```cpp
Error walkArchive(Archive A) {
Error DeferredErrs = Error::success();
for (unsigned I = 0; I != A.numMembers(); ++I) {
auto ChildOrErr = A.getMember(I);
if (auto Err = ChildOrErr.takeError())
if (Err.isA<BadFileFormat>())
DeferredErrs = joinErrors(std::move(DeferredErrs), std::move(Err));
else
return Err;
auto &Child = *ChildOrErr;
// Use Child
...
}
return DeferredErrs;
}
joinErrors例程构建一个名为ErrorList的特殊错误类型,其中包含用户定义的错误列表。handleErrors例程识别这种类型,并尝试按顺序处理每个包含的错误。如果所有包含的错误都可以处理,handleErrors将返回Error::success(),否则handleErrors将连接其余错误并返回结果ErrorList。
3.4.2.9 构建容易出错的迭代器和迭代器范围
上面的归档行走示例按索引检索归档成员,但是这需要相当多的样板文件来进行迭代和错误检查。我们可以使用“容易出错的迭代器”模式来清理这个问题,该模式支持对容易出错的容器(如存档)使用以下自然迭代习语:
Error Err;
for (auto &Child : Ar->children(Err)) {
// Use Child - only enter the loop when it's valid
// Allow early exit from the loop body, since we know that Err is success
// when we're inside the loop.
if (BailOutOn(Child))
return;
...
}
// Check Err after the loop to ensure it didn't break due to an error.
if (Err)
return Err;
为了启用这种习惯用法,容易出错的容器上的迭代器是用自然的风格编写的,它们的++和——操作符被容易出错的Error inc()和Error dec()函数替换。例如:
class FallibleChildIterator {
public:
FallibleChildIterator(Archive &A, unsigned ChildIdx);
Archive::Child &operator*();
friend bool operator==(const ArchiveIterator &LHS,
const ArchiveIterator &RHS);
// operator++/operator-- replaced with fallible increment / decrement:
Error inc() {
if (!A.childValid(ChildIdx + 1))
return make_error<BadArchiveMember>(...);
++ChildIdx;
return Error::success();
}
Error dec() { ... }
};
然后使用fallible_iterator实用程序对这种易出错迭代器接口的实例进行包装,该实用程序提供了操作符++和操作符——,通过在构建时传递给包装器的引用返回任何错误。fallible_iterator包装负责(a)跳在误差范围的结束,和(b)标记错误检查每次迭代器相比,发现是不平等的(特别是:这标志着错误的全身检查基于范围for循环),使早期退出循环,没有多余的错误检查。
错误迭代器接口的实例(例如上面的错误迭代器)使用make_fallible_itr和make_fallible_end函数进行包装。例如:
class Archive {
public:
using child_iterator = fallible_iterator<FallibleChildIterator>;
child_iterator child_begin(Error &Err) {
return make_fallible_itr(FallibleChildIterator(*this, 0), Err);
}
child_iterator child_end() {
return make_fallible_end(FallibleChildIterator(*this, size()));
}
iterator_range<child_iterator> children(Error &Err) {
return make_range(child_begin(Err), child_end());
}
};
使用fallible_iterator实用程序允许自然构造容易出错的迭代器(使用失败的inc和dec操作)和相对自然地使用c++迭代器/循环习惯用法
有关错误及其相关实用程序的更多信息可以在Error.h头文件中找到。
3.5 传递函数和其他可调用对象
有时,您可能希望传递一个函数一个回调对象。为了支持lambda表达式和其他函数对象,你不应该使用传统的C方法来获取函数指针和一个不透明的cookie:
void takeCallback(bool (*Callback)(Function *, void *), void *Cookie);
相反,使用以下方法之一:
3.5.1 函数模板
如果您不介意将函数的定义放入头文件中,请将其设置为可调用类型上的函数模板。
template<typename Callable>
void takeCallback(Callable Callback) {
Callback(1, 2, 3);
}
3.5.2 function_ref类模板
function_ref (doxygen)类模板表示对可调用对象的引用,并在可调用对象的类型上进行模板化。如果在函数返回后不需要保留回调,那么这是向函数传递回调的好选择。这样,function_ref与std::函数的关系就像StringRef与std::string的关系一样。
function_ref<ret(param1, param2,…<="" span=""></ret(param1,>
例如:
void visitBasicBlocks(Function *F, function_ref<bool (BasicBlock*)> Callback) {
for (BasicBlock &BB : *F)
if (Callback(&BB))
return;
}
可以使用以下命令调用:
visitBasicBlocks(F, [&](BasicBlock *BB) {
if (process(BB))
return isEmpty(BB);
return false;
});
注意function_ref对象包含指向外部内存的指针,因此存储类的实例通常是不安全的(除非您知道外部存储将不会被释放)。如果需要此功能,请考虑使用std::函数。function_ref足够小,它应该总是通过值传递。
3.6 LLVM_DEBUG()宏和-debug选项
3.6.1 带有DEBUG_TYPE和-debug-only选项的细粒度调试信息
3.7 Statistic类& -stats选项
3.8 添加调试计数器来帮助调试代码
3.9 调试代码时查看图形
4. 为任务选择正确的数据结构
LLVM /ADT/目录中有大量的数据结构,我们通常使用STL数据结构。本节描述在选择时应该考虑的权衡。
第一步是选择您自己的冒险:您想要顺序容器、类似于集合的容器还是类似于映射的容器?在选择容器时,最重要的是计划如何访问容器的算法属性。基于此,你应该使用:
- 如果需要基于另一个值高效地查找值,则使用类似于映射的容器。类映射容器还支持有效的包含查询(无论键是否在映射中)。类映射容器通常不支持有效的反向映射(值到键)。如果需要,可以使用两个映射。一些类似于映射的容器还支持按顺序高效地遍历键。类映射容器是最昂贵的一种,只有在需要这些功能之一时才使用它们。
- 如果您需要将一堆东西放入一个容器中,这个容器可以自动消除重复。一些类似集合的容器支持按排序顺序对元素进行有效的迭代。类集合容器比顺序容器更昂贵。
- 顺序容器提供了最有效的方法来添加元素,并跟踪它们添加到集合中的顺序。它们允许复制并支持有效的迭代,但不支持基于键的高效查找。
- 字符串容器是用于字符或字节数组的专用顺序容器或引用结构。
- 位容器提供了一种有效的方法来存储和执行数字id集上的set操作,同时自动消除重复。要存储的每个标识符,位容器最多需要1位。
一旦确定了容器的适当类别,您就可以通过明智地选择类别中的成员来微调内存使用、常量因素和缓存访问行为。请注意,常量因素和缓存行为可能很重要。例如,如果您有一个向量,它通常只包含几个元素(但是可以包含许多元素),那么使用SmallVector比使用vector要好得多。这样做可以避免(相对)昂贵的malloc/free调用,这大大降低了向容器添加元素的成本。
4.1 Sequential Containers (std::vector, std::list, etc)
根据您的需要,可以使用各种顺序容器。在本节中选择第一个可以做您想做的事情。
4.1.1 lvm/ADT/ArrayRef.h
llvm::ArrayRef类是接口中使用的首选类,该接口接受内存中元素的顺序列表并从其中读取数据。通过使用ArrayRef,可以向API传递一个固定大小的数组、一个std::vector、一个llvm::SmallVector以及内存中任何相邻的内容。
4.1.2 Fixed Size Arrays
固定大小的数组非常简单和快速。如果您确切地知道您有多少个元素,或者您对您有多少个元素有一个(低)上限,那么它们是很好的。
4.1.3 Heap Allocated Arrays
堆分配数组(new[] + delete[])也很简单。如果元素的数量是可变的,如果您知道在分配数组之前需要多少元素,如果数组通常很大(如果不是,请考虑一个小向量),那么它们是很有用的。堆分配数组的成本是new/delete(又名malloc/free)的成本。还请注意,如果使用构造函数分配类型的数组,则将为数组中的每个元素运行构造函数和析构函数(重新调整大小的向量只构造实际使用的元素)。
4.1.4 llvm/ADT/TinyPtrVector.h
TinyPtrVector是一个高度专门化的集合类,当一个向量有零个或一个元素时,优化它以避免分配。它有两个主要限制:1)它只能保存指针类型的值,2)它不能保存空指针。
由于这个容器高度专门化,所以很少使用它。
4.1.5 llvm/ADT/SmallVector.h
SmallVector<type, n="">是一个看起来和闻起来都像vector的简单类:它支持高效的迭代,以内存顺序排列元素(这样您就可以在元素之间执行指针算术),支持高效的push_back/pop_back操作,支持对其元素的高效随机访问,等等。</type,>
SmallVector的主要优点是它为对象本身中的一些元素(N)分配了空间。因此,如果小向量动态地小于N,则不执行malloc。如果malloc/free调用比处理元素的代码昂贵得多,那么这将是一个巨大的胜利。
这是有利于向量”通常小”(如前辈的数量/继任者的一块通常小于8)。另一方面,这使得SmallVector本身的尺寸大,所以你不想分配很多(这样做会浪费很多空间)。因此,在堆栈上使用小向量是最有用的。
SmallVector还为alloca提供了一个很好的可移植性和高效的替代品。
SmallVector相对于std::vector还有一些其他的小优势,这使得SmallVector<type, 0="">优于std::vector。</type,>
- vector是异常安全的,一些实现具有悲观的特性,当SmallVector移动元素时,它会复制这些元素。
- SmallVector理解llvm::is_trivially_copyable,并积极使用realloc。
- 许多LLVM api都将SmallVectorImpl作为out参数(参见下面的说明)。
- 在64位平台上,N = 0的SmallVector比std::vector小,因为它使用无符号(而不是void*)表示其大小和容量。
请注意
最好使用SmallVectorImpl作为参数类型。
在不关心“小尺寸”(大多数?)的api中,更喜欢使用SmallVectorImpl类,它基本上就是一个“vector
header”(和方法),后面没有分配元素。注意,SmallVector<t,
n="">继承自SmallVectorImpl,所以转换是隐式的,不需要花费任何代价。</t,>如。
// BAD: Clients cannot pass e.g. SmallVector<Foo, 4>.
hardcodedSmallSize(SmallVector<Foo, 2> &Out);
// GOOD: Clients can pass any SmallVector<Foo, N>.
allowsAnySmallSize(SmallVectorImpl &Out);
void someFunc() {
SmallVector<Foo, 8> Vec;
hardcodedSmallSize(Vec); // Error.
allowsAnySmallSize(Vec); // Works.
}
尽管它的名称中有“Impl”,但它的使用如此广泛,以至于它实际上不再是“实现的私有”了。像SmallVectorHeader这样的名称更合适。
4.1.6
std: vector很受欢迎和尊重。但是,由于上面列出的优点,SmallVector<t, 0="">通常是更好的选择。</t,>当您需要存储超过UINT32_MAX的元素或与期望vector:)的代码进行接口时,vector仍然很有用。
关于std::vector:避免这样的代码:
for ( … ) {
std::vector V;
// make use of V.
}
而是写成:
std::vector V;
for ( … ) {
// make use of V.
V.clear();
}
这样做将节省(至少)一个堆分配,并且循环的每次迭代都是空闲的。
4.1.7
在某种意义上,deque是std::vector的广义版本。与std::vector类似,它提供了常量时间随机访问和其他类似属性,但它也提供了对列表前端的有效访问。它不能保证内存中元素的连续性。
作为这种额外灵活性的交换,std::deque的常数因子成本显著高于std::vector。如果可能的话,使用std::vector或其他更便宜的工具。
4.1.8
list是一个非常低效的类,很少有用。它为插入其中的每个元素执行堆分配,因此具有非常高的常数因子,特别是对于小数据类型。list也只支持双向迭代,不支持随机访问迭代。
作为这种高成本的交换,std::list支持对列表两端的有效访问(与std::deque类似,但与std::vector或SmallVector不同)。此外,std::list的迭代器失效特性比vector类更强:在列表中插入或删除元素不会使迭代器或指向列表中其他元素的指针失效。
4.1.9 llvm/ADT/ilist.h
ilist实现了一个“侵入式”的双链表。它是侵入性的,因为它要求元素存储并提供对列表的prev/next指针的访问。
ilist具有与std::list相同的缺点,而且还需要为元素类型实现ilist_traits,但是它提供了一些新的特性。特别是,它可以有效地存储多态对象,在插入或从列表中删除元素时通知traits类,并且ilists保证支持常量时间拼接操作。
这些属性正是我们想要的,比如指令和基本块,这就是为什么这些是用ilists实现的。
有关的兴趣类别可在下面的小节解释:
- ilist_traits
- iplist
- llvm / ADT / ilist_node.h
- Sentinels
4.1.10 llvm/ADT/PackedVector.h
适用于仅为每个值存储少量位的值向量。除了类向量容器的标准操作之外,它还可以执行“或”集合操作。
例如:
enum State {
None = 0x0,
FirstCondition = 0x1,
SecondCondition = 0x2,
Both = 0x3
};
State get() {
PackedVector<State, 2> Vec1;
Vec1.push_back(FirstCondition);
PackedVector<State, 2> Vec2;
Vec2.push_back(SecondCondition);
Vec1 |= Vec2;
return Vec1[0]; // returns 'Both'.
}
4.1.11 ilist_traits
ilist_traits是ilist的定制机制。iplist(因此ilist)公开派生自这个traits类。
4.1.12 iplist
iplist是ilist的基础,因此支持稍微窄一点的接口。值得注意的是,没有来自t&r的插入器。
ilist_traits是这个类的公共基础,可以用于多种定制。
4.1.13 llvm/ADT/ilist_node.h
ilist_node以默认方式实现了ilist(和类似容器)所期望的正向和反向链接。
ilist_nodes被嵌入到节点类型T中,通常T公开派生自ilist_node。
4.1.14 Sentinels
4.1.15 Other Sequential Container options
4.2 String-like containers
4.2.1 llvm/ADT/StringRef.h
4.2.2 llvm/ADT/Twine.h
4.2.3 llvm/ADT/SmallString.h
4.2.4 std::string
4.3 Set-Like Containers (std::set, SmallSet, SetVector, etc)
4.3.1 A sorted ‘vector’
4.3.2 llvm/ADT/SmallSet.h
4.3.3 llvm/ADT/SmallPtrSet.h
4.3.4 llvm/ADT/StringSet.h
4.3.5 llvm/ADT/DenseSet.h
4.3.6 llvm/ADT/SparseSet.h
4.3.7 llvm/ADT/SparseMultiSet.h
4.3.8 llvm/ADT/FoldingSet.h
4.3.9
4.3.10 llvm/ADT/SetVector.h
4.3.11 llvm/ADT/UniqueVector.h
4.3.12 llvm/ADT/ImmutableSet.h
4.3.13 Other Set-Like Container Options
4.4 Map-Like Containers (std::map, DenseMap, etc)
4.4.1 A sorted ‘vector’
4.4.2 llvm/ADT/StringMap.h
4.4.3 llvm/ADT/IndexedMap.h
4.4.4 llvm/ADT/DenseMap.h
4.4.5 llvm/IR/ValueMap.h
4.4.6 llvm/ADT/IntervalMap.h
4.4.7
4.4.8 llvm/ADT/MapVector.h
4.4.9 llvm/ADT/IntEqClasses.h
4.4.10 llvm/ADT/ImmutableMap.h
4.4.11 Other Map-Like Container Options
4.5 Bit storage containers (BitVector, SparseBitVector)
4.5.1 BitVector
4.5.2 SmallBitVector
4.5.3 SparseBitVector
5. 调试
6. 常见操作的有用提示
本节描述如何执行一些非常简单的LLVM代码转换。这意味着要给出常用习惯用法的示例,展示LLVM转换的实用方面。
因为这是一个“how-to”部分,所以您还应该阅读将要使用的主要类。核心LLVM类层次结构参考资料包含您应该了解的主要类的详细信息和描述。
6.1 基本检查和遍历例程
LLVM编译器基础架构有许多可以遍历的不同数据结构。以C++标准模板库为例,用于遍历这些不同数据结构的技术基本上是相同的。对于一个可枚举的值序列,XXXbegin()函数(或方法)返回一个序列开头的迭代器,在XXXend()函数返回一个迭代器,该迭代器指向序列最后一个有效元素之后的元素,这两个操作之间有一些XXXiterator数据类型是常见的。
因为迭代的模式在程序表示的许多不同方面都是通用的,所以可以在它们上使用标准模板库算法,并且更容易记住如何迭代。首先,我们展示一些需要遍历的数据结构的常见示例。以非常相似的方式遍历其他数据结构。
6.1.1 遍历一个Function中的BasicBlock
有一个你想要以某种方式转换的函数实例是很常见的;特别是,您希望操作它的基本块。为了实现这一点,您需要遍历构成该Function的所有BasicBlocks。下面是打印一个BasicBlock的名称和它包含的Instructions数的例子:
Function &Func = ...
for (BasicBlock &BB : Func)
// 如果有BasicBlock,则打印它的名称,然后打印它包含的Instructions数
errs() << "Basic block (name=" << BB.getName() << ") has "
<< BB.size() << " instructions.\n";
6.1.2 遍历一个BasicBlock中的Instruction
就像在函数中处理基本块一样,很容易遍历组成基本块的各个指令。这是一个代码片段,打印出在一个基本块的每个指令:
BasicBlock& BB = ...
for (Instruction &I : BB)
// 由于操作符<<(ostream&,…)为Instruction&重载,所以下一条指令可用
errs() << I << "\n";
然而,这并不是打印BasicBlock内容的最佳方式!由于ostream操作符实际上重载了您所关心的所有内容,所以您可以调用基本块本身上的打印例程:errs() << BB << "\n";
。
6.1.3 遍历一个Function中的Instruction
如果您发现您通常遍历函数的基本块,然后遍历基本块的指令,那么应该使用InstIterator
。您需要include llvm/IR/InstIterator.h(doxygen),然后在代码中显式实例化InstIterator。下面是一个小例子,展示了如何将函数中的所有指令转储到标准错误流:
#include "llvm/IR/InstIterator.h"
// F是指向函数实例的指针
for (inst_iterator I = inst_begin(F), E = inst_end(F); I != E; ++I)
errs() << *I << "\n";
很容易,不是吗?您还可以使用InstIterator来用工作列表的初始内容填充工作列表。例如,如果你想初始化一个工作列表来包含函数F中的所有指令,你需要做的就是:
std::set<Instruction*> worklist;
// 或者更好的是:SmallPtrSet<Instruction*, 64> worklist;
for (inst_iterator I = inst_begin(F), E = inst_end(F); I != E; ++I)
worklist.insert(&*I);
STL set工作列表现在将包含F指向的函数中的所有指令。
6.1.4 将一个 iterator 转换为一个类指针(反之亦然)
有时候,当您手头只有一个iterator
时,获取一个类实例的引用(或指针)是很有用的。从 iterator 中提取引用或指针非常简单。假设 i 是一个BasicBlock::iterator,j是一个BasicBlock::const_iterator:
Instruction& inst = *i; // 获取对指令引用的引用
Instruction* pinst = &*i; // 获取指向指令引用的指针
const Instruction& inst = *j;
但是,您将在LLVM框架中使用的 iterator 是特殊的:它们将在需要的时候自动转换为 ptr-to-instance 类型。原本是取消对 iterator 的引用,然后获取结果的地址;代替的是:您可以简单地将 iterator 分配给适当的指针类型,然后您将获得操作的dereference和address作为分配的结果(在幕后,这是重载转换机制的结果)。因此上一个例子的第二行Instruction *pinst = &*i;
,在语义上等价于:Instruction *pinst = i;
也可以将一个类指针转换为相应的 iterator ,这是一个常量时间操作(非常有效)。下面的代码片段演示了使用LLVM iterator 提供的转换构造函数。通过使用这些,您可以显式地获取某个东西的 iterator,而无需通过对某个结构进行迭代来实际获取它:
void printNextInstruction(Instruction* inst) {
BasicBlock::iterator it(inst);
++it; // 在这一行之后,it引用*inst之后的指令
if (it != inst->getParent()->end()) errs() << *it << "\n";
}
不幸的是,这些隐式转换是有代价的;它们阻止这些 iterator 遵守标准 iterator 约定,从而使它们不能与标准算法和容器一起使用。例如,它们阻止编译下面的代码,其中B是一个BasicBlock:
llvm::SmallVector<llvm::Instruction *, 16>(B->begin(), B->end());
因此,这些隐式转换可能会在某一天被删除,并且操作符*将返回指针而不是引用。
6.1.5 查找调用站点:一个稍微复杂一点的示例
假设您正在编写一个FunctionPass
,并且希望计算整个模块(即跨所有函数)中某个函数(即某个Function *)已经在作用域中的所有位置(被调用的次数)。稍后您将了解到,您可能希望使用 InstVisitor
以一种更直接的方式来实现这一点,但是这个示例将允许我们探索如果没有InstVisitor,您将如何实现这一点。在伪代码中,我们要做的是:
initialize callCounter to zero // 将callCounter初始化为零
for each Function f in the Module
for each BasicBlock b in f
for each Instruction i in b
if (i is a CallInst and calls the given function) // i是一个CallInst,它调用给定的函数
increment callCounter // 增量callCounter
实际的代码是(记住,因为我们在编写FunctionPass,我们的FunctionPass派生类只需要重载runOnFunction方法):
Function* targetFunc = ...;
class OurFunctionPass : public FunctionPass {
public:
OurFunctionPass(): callCounter(0) { }
virtual runOnFunction(Function& F) {
for (BasicBlock &B : F) {
for (Instruction &I: B) {
if (auto *CallInst = dyn_cast<CallInst>(&I)) {
// 我们知道我们已经遇到了一个调用指令,所以我们需要确定它是否是m_func指向的函数的调用。
if (CallInst->getCalledFunction() == targetFunc)
++callCounter;
}
}
}
}
private:
unsigned callCounter;
};
6.1.6 以相同的方式处理 calls 和 invokes
您可能已经注意到,前面的示例有些过于简化,因为它没有处理由‘invoke’指令生成的调用站点。在这种情况下,以及在其他情况下,您可能会发现您希望以同样的方式处理 CallInsts 和 InvokeInsts,即使它们最特定的公共基类是 Instruction,其中也包含许多不那么密切相关的东西。对于这些情况,LLVM提供了一个方便的wrapper类 CallSite (doxygen),它本质上是一个围绕 Instruction 指针的wrapper,它有一些方法提供 CallInsts 和 InvokeInsts 共有的功能。
该类具有“值语义”:它应该通过值传递,而不是通过引用传递,并且不应该使用 new 或 delete 操作符动态分配或释放该类。它具有高效的可复制性、可分配性和可构造性,其成本相当于一个空指针的成本。如果你看它的定义,它只有一个指针成员。
6.1.7 遍历 def-use 和 use-def 链
通常,我们可能有一个Value
类的实例(doxygen),我们希望确定哪些Users
使用这个值。具有特定Value
的所有Users
的列表称为def-use
链。例如,我们有一个 Function* F 指向一个特定的函数 foo。找到所有使用 foo 的指令就像遍历 F 的def-use
链一样简单:
Function *F = ...;
for (User *U : F->users()) {
if (Instruction *Inst = dyn_cast<Instruction>(U)) {
errs() << "F is used in instruction:\n";
errs() << *Inst << "\n";
}
或者,通常有一个User
类的实例(doxygen),并且需要知道它使用什么Values
。一个User
使用的所有Values
的列表称为use-def
链。类Instruction
的实例是常见的User
,所以我们可能需要遍历特定Instruction使用的所有values(即特定Instruction的操作数):
Instruction *pi = ...;
for (Use &U : pi->operands()) {
Value *v = U.get();
// ...
}
将对象声明为 const 是实现无变化算法(如分析等)的一个重要工具。为此,上面的iterators有两种固定的风格:Value::const_use_iterator
和Value::const_op_iterator
。当分别调用 const Value*s
或 const User*s
上的use/op_begin()
时,它们会自动出现。在取消引用后,它们返回const Use*s
。否则,上述模式将保持不变。
6.1.8 遍历块的前置和后继
使用“llvm/IR/CFG.h”中定义的例程,遍历块的前置和后继是非常容易的。只需使用这样的代码来遍历所有BB的前置:
#include "llvm/IR/CFG.h"
BasicBlock *BB = ...;
for (BasicBlock *Pred : predecessors(BB)) {
// ...
}
类似地,要遍历后继,可以使用successors
。
6.2 做些简单的改变
LLVM基础架构中有一些基本的转换操作值得了解。在执行转换时,操作基本块的内容是相当常见的。本节描述了一些常用的方法,并给出了示例代码。
6.2.1 创建和插入新 Instructions
- 实例化 Instructions
创建Instructions非常简单:只需调用该类指令的构造函数来实例化并提供必要的参数。例如,AllocaInst
只需要提供一个(const-ptr-to) Type。因此:
这将在运行时创建一个 AllocaInst 实例,该实例表示当前堆栈帧中一个整数的分配。每个 Instruction 子类都可能有不同的默认参数,这些参数会改变这个指令的语义,所以请参考 Instruction子类的doxygen文档,以获得您感兴趣的要实例化的子类的内容。auto *ai = new AllocaInst(Type::Int32Ty);
- 命名值
如果可以的话,命名指令的值是非常有用的,因为这有助于调试您的转换。如果您最终查看生成的LLVM机器码,那么您肯定希望有与指令结果关联的逻辑名称!通过为Instruction
构造函数的Name
(缺省)参数提供一个值,您可以将逻辑名称与运行时指令执行的结果关联起来。例如,假设我正在编写一个转换,它动态地为堆栈上的一个整数分配空间,这个整数将被其他代码用作某种索引。为此,我将AllocaInst
放在某个 Function 的第一个 BasicBlock 的第一个 point 上,并打算在同一个 Function 中使用它。我可能会做:
其中auto *pa = new AllocaInst(Type::Int32Ty, 0, "indexLoc");
indexLoc
现在是指令执行值的逻辑名称,它是指向运行时堆栈上整数的指针。 - 插入 Instructions
从本质上讲,有三种方法可以将一条 Instruction 插入到构成一个 BasicBlock 的现有指令序列中:- 插入到显式指令列表中
给定一个BasicBlock* pb
,该 BasicBlock 中的一个Instruction* pi
,以及我们希望在 *pi 之前插入的一条新创建的instruction
,我们执行以下操作:
附加到一个 BasicBlock 末尾是如此常见,以至于Instruction类和Instruction派生类提供构造函数,构造函数接受要附加到 BasicBlock 的指针。例如代码如下:BasicBlock *pb = ...; Instruction *pi = ...; auto *newInst = new Instruction(...); pb->getInstList().insert(pi, newInst); // Inserts newInst before pi in pb
就变成:BasicBlock *pb = ...; auto *newInst = new Instruction(...); pb->getInstList().push_back(newInst); // Appends newInst to pb
这是非常简洁的,特别的是如果您正在创建长指令流。BasicBlock *pb = ...; auto *newInst = new Instruction(..., pb);
- 插入到隐式指令列表中
已经在 BasicBlocks 中的 Instruction 实例隐式地与现有的指令列表关联:包含基本块的指令列表。因此,我们可以完成与上述代码相同的事情,而不需要给一个基本块:
实际上,这一系列步骤发生得非常频繁,以至于 Instruction 类和 Instruction 派生类提供构造函数,构造函数(作为默认参数)接受一个指向一条 Instruction 的指针,新创建的 Instruction 应该位于这个指令之前。也就是说,Instruction 构造函数能够将新创建的实例插入到所提供的指令的 BasicBlock 中,就在该指令之前。使用带有一个 insertBefore (默认)参数的 Instruction 构造函数,上面的代码变成:Instruction *pi = ...; auto *newInst = new Instruction(...); pi->getParent()->getInstList().insert(pi, newInst);
这是非常简洁的,特别是当您创建了很多指令并将它们添加到 BasicBlocks 中时。Instruction* pi = ...; auto *newInst = new Instruction(..., pi);
- 使用 IRBuilder 的一个实例进行插入
使用前面的方法插入几条 Instructions 可能非常费力。IRBuilder
是一个方便的类,可以用来在一个 BasicBlock 的末尾或某个特定 Instruction 之前添加多个指令。它还支持常量折叠和重命名命名寄存器(参见IRBuilder
的模板参数)。
下面的示例演示了IRBuilder
的一个非常简单的用法,其中在指令 pi 之前插入了三条指令。前两条指令是 Call 指令,第三条指令乘以这两条调用的返回值。
下面的示例与上面的示例类似,只是创建的 IRBuilder 在 BasicBlock pb 的末尾插入指令。Instruction *pi = ...; IRBuilder<> Builder(pi); CallInst* callOne = Builder.CreateCall(...); CallInst* callTwo = Builder.CreateCall(...); Value* result = Builder.CreateMul(callOne, callTwo);
有关 IRBuilder 的实际使用,请参见 Kaleidoscope:对LLVM IR的代码生成。BasicBlock *pb = ...; IRBuilder<> Builder(pb); CallInst* callOne = Builder.CreateCall(...); CallInst* callTwo = Builder.CreateCall(...); Value* result = Builder.CreateMul(callOne, callTwo);
- 插入到显式指令列表中
6.2.2 删除 Instructions
从构成一个 BasicBlock 的现有指令序列中删除一条指令非常简单:只需调用该指令的eraseFromParent()
方法。例如:
Instruction *I = .. ;
I->eraseFromParent();
这将从其包含的基本块中断开指令的链接并删除它。如果只是想从包含基本块的指令中断开链接,而不是删除它,可以使用removeFromParent()
方法。
6.2.3 用另一个 Value 替换一条 Instruction
6.2.3.1 取代个别的 Instructions
包含 “llvm/Transforms/Utils/BasicBlockUtils.h” 允许使用两个非常有用的替换函数:ReplaceInstWithValue
和ReplaceInstWithInst
。
6.2.3.2 删除 Instructions
- ReplaceInstWithValue
这个函数用一个 Value 替换给定 Instruction 的所有用法,然后删除原始指令。下面的示例演示了如何用一个指向整数的空指针替换为单个整数分配内存的特定AllocaInst的结果。AllocaInst* instToReplace = ...; BasicBlock::iterator ii(instToReplace); ReplaceInstWithValue(instToReplace->getParent()->getInstList(), ii, Constant::getNullValue(PointerType::getUnqual(Type::Int32Ty)));
- ReplaceInstWithInst
该函数用另一条指令替换一条特定的指令,将新指令插入到旧指令所在的基本块中,并用新指令替换旧指令的任何用法。下面的示例演示了如何用另一个 AllocaInst 替换一个 AllocaInst:AllocaInst* instToReplace = ...; BasicBlock::iterator ii(instToReplace); ReplaceInstWithInst(instToReplace->getParent()->getInstList(), ii, new AllocaInst(Type::Int32Ty, 0, "ptrToReplacedInt"));
6.2.3.3 替换 Users 和 Values 的多个使用
您可以使用Value::replaceAllUsesWith
和User::replaceUsesOfWith
来一次更改多个使用。有关Value类和User类的更多信息,请参见doxygen文档。
6.2.4 删除 GlobalVariables
从一个 Module 中删除一个全局变量与删除一条指令一样简单。首先,必须有一个指向要删除的全局变量的指针。您可以使用这个指针将它从其parent(即Module)中删除。例如:
GlobalVariable *GV = .. ;
GV->eraseFromParent();
7. 线程和LLVM
本节描述LLVM APIs与多线程的交互,包括客户端应用程序的交互和JIT中的交互,以及托管应用程序中的交互。
注意,LLVM对多线程的支持仍然相对较年轻。在2.5版之前,支持线程托管应用程序的执行,但不支持线程客户机访问APIs。虽然现在支持这个用例,但是客户端必须遵守下面指定的指导原则,以确保在多线程模式下正确操作。
注意,在类unix平台上,为了支持线程操作,LLVM需要GCC的原子内部特性。如果需要在没有合适的现代系统编译器的平台上使用支持多线程的LLVM,可以考虑在单线程模式下编译LLVM和LLVM-GCC,并使用生成的编译器构建支持多线程的LLVM副本。
7.1 使用llvm_shutdown()结束执行
7.2 使用ManagedStatic延迟初始化
7.3 使用LLVMContext实现隔离
7.4 线程和JIT
8. 高级的主题
本节描述了大多数客户端不需要知道的一些高级或晦涩的API。这些API往往管理LLVM系统的内部工作,只需要在不寻常的情况下访问。
8.1 ValueSymbolTable类
ValueSymbolTable (doxygen)类提供了一个符号表,Function和Module类使用这个符号表来命名value定义。符号表可以为任何Value提供一个名称。
注意,SymbolTable类不应该被大多数客户机直接访问。它只应该在需要遍历符号表名称本身时使用,这是非常特殊的用途。注意,并非所有LLVM Values都有名称,没有名称的Value
(即它们有一个空名称)在符号表中不存在。
符号表支持使用begin/end/iterator
对符号表中的values
进行迭代,并支持查询符号表中是否有特定的名称(通过lookup
)。ValueSymbolTable类不公开任何public改变对象属性的方法 ,而是简单地对一个value调用setName,这将会自动将其插入到适当的符号表中。
8.2 User
和owned Use
类的内存布局
User
(doxygen)类为表示User对其他Value instances的所有权提供了基础。Use
(doxygen) helper类用于簿记和促进*O(1)*的添加和删除。
8.2.1 User
和Use
对象之间的交互和关系
User
的一个子类可以选择合并它的Use
对象,也可以通过指针在行外引用它们。混合变量(有些Use
s内联,有些挂起)是不切实际的,打破属于同一User
的Use
对象组成连续数组的不变量。
我们在User(子)类有2种不同的布局:
- Layout a)
Use对象位于User对象的内部(resp. 在固定偏移量),它们的数量是固定的 - Layout b)
Use对象由指向User对象的一个数组的一个指针引用,它们的数量可能是可变的。
从v2.4开始,每个布局仍然拥有指向使用数组开始的直接指针。虽然Layout a)不是强制性的,但是为了简单起见,我们坚持使用这种冗余。User对象还存储它拥有的使用对象的数量。(从理论上讲,这个信息也可以通过下面的方案计算出来。)
特殊形式的分配操作符(操作符new)强制执行以下内存布局: - Layout a)通过Use[]数组在User对象前面加上前缀来建模。
...---.---.---.---.-------... | P | P | P | P | User '''---'---'---'---'-------'''
- Layout b)通过指向Use[]数组来建模。
.-------... | User '-------''' | v .---.---.---.---... | P | P | P | P | '---'---'---'---'''
(在上面的图中,“P
”代表Use**
,它存储在成员Use::Prev
中的每个Use
对象中)
8.2.2 waymarking算法
8.2.3 参考实现
8.2.4 标签注意事项
8.3 设计类型层次结构和多态接口
8.4 ABI打破检查
9. 核心LLVM类层次结构参考资料
#include “llvm/IR/Type.h”
header source: Type.h
doxygen info: Type Clases
核心LLVM类是表示被检查或转换的程序的主要方法。核心LLVM类在 include/llvm/IR
目录的头文件中定义,并在lib/IR
目录中实现。值得注意的是,由于历史原因,这个库被称为libLLVMCore.so
,并不是你所想的libLLVMIR.so
。
9.1 Type
类和派生类型
Type
是所有type类的一个超类。每个Value
都有一个Type
。Type不能直接实例化,只能通过其子类实例化。某些基本类型(VoidType、LabelType、FloatType和DoubleType)有隐藏的子类。之所以隐藏它们,是因为除了Type类提供的功能之外,它们没有提供任何有用的功能,除了将它们与Type的其他子类区分开来之外。
所有其他类型都是DerivedType
的子类。Types
可以被命名,但这不是必需的。一个给定形状在任何时候都只存在一个实例。这允许使用Type实例的地址相等来执行type相等。也就是说,给定两个Type*值,如果指针相同,则types相同。
9.1.1 重要的Public方法
bool isIntegerTy() const
:对任何整数类型返回true。bool isFloatingPointTy()
:如果这是五种浮点类型之一,则返回true。bool issize()
:如果类型的大小已知,则返回true。没有大小的是抽象类型、标签和void。
9.1.2 重要的派生类型
- IntegerType
DerivedType的子类,表示任意位宽的整数类型。在IntegerType::MIN_INT_BITS (1)
和IntegerType::MAX_INT_BITS (~8 million)
之间的任何位宽都可以被表示。static const IntegerType* get(unsigned NumBits)
:获取一个特定位宽的一个整数类型。unsigned getBitWidth() const
:获取一个整数类型的位宽。
- SequentialType
它由ArrayType
和VectorType
子类化。const Type * getElementType() const
:返回顺序类型中每个元素的类型。uint64_t getNumElements() const
:返回序列类型中的元素数量。
- ArrayType
这是SequentialType
的子类,定义了数组类型的接口。 - PointerType
指针类型的Type
子类。 - VectorType
向量类型的SequentialType
的子类。向量类型与ArrayType
类似,但区别在于它是一个first class type,而ArrayType不是。向量类型用于向量操作,通常是一个整数或浮点类型的小向量。 - StructType
DerivedTypes
的子类,用于struct类型。 - FunctionType
DerivedTypes
的子类,用于function类型。bool isVarArg() cons
:如果它是一个vararg函数,则返回true。const Type * getReturnType() const
:返回函数的返回类型。const Type * getParamType (unsigned i)
:返回第i个参数的类型。const unsigned getNumParams() const
:返回形式参数的数量。
9.2 Module类
#include “llvm/IR/Module.h”
header source: Module.h
doxygen info: Module Class
Module
类表示LLVM程序中的顶层结构。一个LLVM Module实际上要么是原始程序的一个翻译单元,要么是链接器合并的几个翻译单元的一个组合。Module
类跟踪一个Functions列表、一个GlobalVariables列表和一个SymbolTable。此外,它还包含一些有用的成员函数,这些函数试图简化常见操作。
9.2.1 Module类的重要Public成员
Module::Module(std::string name = "")
构造一个Module很容易。您可以选择为它提供一个名称(可能基于翻译单元的名称)。Module::iterator
—— 函数列表iterator的类型定义
Module::const_iterator
—— const_iterator的类型定义。
begin(), end(), size(), empty()
这些转发方法使访问Module对象的Function列表的内容变得很容易。Module::FunctionListType &getFunctionList()
返回Function列表。当您需要更新列表或执行没有转发方法的复杂操作时,这是必需的。Module::global_iterator
—— 全局变量列表iterator的类型定义
Module::const_global_iterator
—— const_iterator的类型定义。
global_begin(), global_end(), global_size(), global_empty()
这些转发方法使访问Module对象的GlobalVariable列表的内容变得很容易。Module::GlobalListType &getGlobalList()
返回GlobalVariables列表。当您需要更新列表或执行没有转发方法的复杂操作时,这是必需的。SymbolTable *getSymbolTable()
返回对这个Module的SymbolTable的一个引用。Function *getFunction(StringRef Name) const
在Module SymbolTable中查找指定的函数。如果不存在,返回null。FunctionCallee getOrInsertFunction(const std::string &Name, const FunctionType *T)
在Module SymbolTable中查找指定的函数。如果它不存在,则为函数添加一个外部声明并返回它。注意,已经存在的函数签名可能与请求的签名不匹配。因此,为了支持将结果直接传递给EmitCall的常见用法,返回类型是{FunctionType *T, Constant *FunctionPtr}
的一个结构体,而不是具有潜在的意外签名的简单Function*
。std::string getTypeName(const Type *Ty)
如果指定Type的SymbolTable中至少有一个条目,则返回它。否则返回空字符串。bool addTypeName(const std::string &Name, const Type *Ty)
在将Name映射到Ty的SymbolTable中插入一个条目。如果已经有该名称的条目,则返回true,并且不修改SymbolTable。
9.3 Value类
#include “llvm/IR/Value.h”
header source: Value.h
doxygen info: Value Class
Value
类是LLVM源库中最重要的类。它表示一个类型化值,可以(除其他外)用作一条指令的操作数。有许多不同类型的Values,比如常量、参数。甚至指令和函数也是Values。
一个特定的Value可以在程序的LLVM表示中多次使用。例如,一个函数的一个传入参数(用Argument
类的一个实例表示)被引用该参数的函数中的每条指令“使用”。为了跟踪这种关系,Value类保存了使用它的所有Users的一个列表(User类是LLVM图中所有可以引用Values的节点的基类)。这个use列表是LLVM在程序中表示def-use
信息的方式,并且可以通过use_*
方法访问,如下所示。
因为LLVM是一个类型化表示,所以每个LLVM Value都是类型化的,并且这种Type可以通过getType()
方法获得。此外,所有LLVM values都可以被命名。Value的“name”是可在LLVM代码中打印的一个符号字符串:
%foo = add i32 1, 2
这个指令的名称是“foo”。注意,任何值的名称都可能丢失(一个空字符串),所以名称应该只用于调试(使源代码更容易阅读,调试打印输出),不应该用于跟踪值或在它们之间映射。为此,使用指向这个Value本身的一个std::map
代替。
LLVM的一个重要方面是,SSA变量和生成它的操作之间没有区别。因此,任何对指令生成的值的引用(例如,作为传入参数可用的值)都表示为指向表示该值的类实例的直接指针。虽然这可能需要一些时间来适应,但它简化了表示,使操作更容易。
9.3.1 Value类的重要Public成员
Value::use_iterator
—— use-list上的iterator的类型定义
Value::const_use_iterator
—— use-list上的const_iterator的类型定义
unsigned use_size()
—— 返回这个value的users数量。
bool use_empty()
—— 如果没有users,返回true。
use_iterator use_begin()
—— 获取指向use-list的开始的一个迭代器。
use_iterator use_end()
—— 获取指向use-list的结尾的一个迭代器。
User *use_back()
—— 返回列表中的最后一个元素。
这些方法是访问LLVM中的def-use
信息的接口。与LLVM中的所有其他iterators一样,命名约定遵循STL定义的约定。Type *getType() const
这个方法返回Value的Type。bool hasName() const
std::string getName() const
void setName(const std::string &Name)
这类方法用于访问和为Value分配名称,请注意上面的预防措施。void replaceAllUsesWith(Value *V)
此方法遍历一个Value
的use列表
,它更改当前value的所有Users
以引用“V”。例如,如果您检测到一条指令总是产生一个常量值(例如通过常量折叠),您可以像这样用常量替换该指令的所有用法:
Inst->replaceAllUsesWith(ConstVal);
9.4 User类
#include “llvm/IR/User.h”
header source: User.h
doxygen info: User Class
Superclass: Value
User
类是所有可能引用Values
的LLVM节点的公共基类。它公开了一个“操作数”
列表,这些“操作数”是User
引用的所有Values
。User类本身是Value的子类。
User的操作数直接指向它引用的LLVM Value。因为LLVM使用静态单赋值(SSA)表单,所以只能引用一个定义,从而允许这种直接连接。这个连接在LLVM中提供use-def信息。
9.4.1 User类的重要Public成员
User类以两种方式公开操作数列表:通过一个索引访问接口和一个基于iterator的接口。
Value *getOperand(unsigned i)
unsigned getNumOperands()
这两种方法以一个方便直接访问的形式公开User的操作数。User::op_iterator
—— 操作数列表上的iterator的类型定义
op_iterator op_begin()
—— 获取指向操作数列表的开始的一个迭代器。
op_iterator op_end()
—— 获取指向操作数列表的末尾的一个迭代器。
这些方法一起组成了一个User操作数的基于iterator的接口。
9.5 Instruction类
#include “llvm/IR/Instruction.h”
header source: Instruction.h
doxygen info: Instruction Class
Superclasses: User, Value
Instruction
类是所有LLVM指令的公共基类。它只提供了几个方法,但是是一个非常常用的类。Instruction
类本身跟踪的主要数据是操作码(指令类型)和嵌入Instruction的父BasicBlock。为了表示一个特定类型的指令,使用了众多Instruction子类中的一个。
因为Instruction
类是User
类的子类,所以可以像访问其他Users一样访问它的操作数(使用getOperand()/getNumOperands()
和op_begin()/op_end()
方法)。Instruction类的一个重要文件是llvm/Instruction.def
文件。这个文件包含一些关于LLVM中各种不同类型指令的元数据
。它描述了用作操作码
的enum值(例如,Instruction::Add
和Instruction::ICmp
),以及实现该指令的具体Instruction子类(例如,BinaryOperator和CmpInst)。不幸的是,这个文件中宏的使用混淆了doxygen,所以这些enum值没有正确地显示在doxygen输出中。
9.5.1 Instruction类的重要子类
- BinaryOperator
这个子类表示所有两个操作数指令,它们的操作数必须是相同的类型,比较指令除外。 - CastInst
这个子类是12个casting指令的父类。它提供了对cast指令的通用操作。 - CmpInst
这个子类表示两个比较指令,ICmpInst(整数操作数)和FCmpInst(浮点操作数)。
9.5.2 Instruction类的重要Public成员
- BasicBlock *getParent()
返回嵌入该 Instruction 的BasicBlock。 - bool mayWriteToMemory()
如果指令(即call、free、invoke或store)写入内存,则返回true。 - unsigned getOpcode()
返回该 Instruction 的操作码。 - Instruction *clone() const
返回指定指令的另一个实例,该实例在所有方面与原始指令相同,只是该指令没有parent(即没有嵌入到BasicBlock中),而且没有名称。
9.6 Constant类和子类
Constant
表示不同类型常量的基类。它由ConstantInt
、ConstantArray
等构成子类,用于表示各种类型的Constants。GlobalValue也是一个子类,它表示全局变量或函数的地址。
9.6.1 Constant类的重要子类
ConstantInt
:Constant
的子类表示任意宽度的一个整数常量。const APInt& getValue() const
:返回这个常量的底层值,一个APInt值。int64_t getSExtValue() const
:通过符号扩展将底层APInt值转换为int64_t。如果APInt的值(而不是位宽)太大,无法容纳int64_t,则会生成一个断言。由于这个原因,不鼓励使用这种方法。uint64_t getZExtValue() const
:通过zero扩展将底层APInt值转换为uint64_t。如果APInt的值(而不是位宽)太大,无法放入uint64_t中,则会生成一个断言。由于这个原因,不鼓励使用这种方法。static ConstantInt* get(const APInt& Val)
:返回代表Val提供的值的ConstantInt对象。该类型被暗示为与Val的位宽相对应的整数类型。static ConstantInt* get(const Type *Ty, uint64_t Val)
:返回代表Val为整数类型Ty提供的值的ConstantInt对象。
ConstantFP
:这个类表示一个浮点常量。double getValue() const
:返回这个常量的基础值。
ConstantArray
:这表示一个常量数组。const std::vector<Use> &getValues() const
:返回组成这个数组的一个组件常量向量。
ConstantStruct
:这表示一个常量Struct。const std::vector<Use> &getValues() const
:返回组成这个struct的一个组件常量向量。
GlobalValue
:它表示一个全局变量或函数。在这两种情况下,值都是一个常量固定地址(链接之后)。
9.7 GlobalValue类
9.7.1 GlobalValue类的重要Public成员
9.8 Function类
#include “llvm/IR/Function.h”
header source: Function.h
doxygen info: Function Class
Superclasses: GlobalValue, Constant, User, Value
Function
类表示LLVM中的一个过程。它实际上是LLVM层次结构中比较复杂的类之一,因为它必须跟踪大量数据。Function类跟踪基本块列表、形式参数列表和符号表。
基本块列表是函数对象中最常用的部分。该列表强制函数中块的隐式排序,这指示代码将如何由后端布局。此外,第一个基本块是函数的隐式入口节点。在LLVM中显式地分支到这个初始块是不合法的。不存在隐式的退出节点,实际上一个函数可能有多个退出节点。如果BasicBlock列表是空的,这表明函数实际上是一个函数声明:函数的实际主体还没有被链接进来。
除了基本块列表之外,函数类还跟踪函数接收到的形式参数列表。这个容器管理参数节点的生存期,就像BasicBlock列表管理BasicBlock一样。
SymbolTable是一个很少使用的LLVM特性,只在必须按名称查找值时才使用。除此之外,符号表还用于内部,以确保函数体中指令、基本块或参数的名称之间没有冲突。
注意,函数是一个全局值,因此也是一个常量。函数的值是它的地址(链接后),它保证是常量。
9.8.1 Function类的重要Public成员
Function(const FunctionType *Ty, LinkageTypes Linkage, const std::string &N = "", Module* Parent = 0)
构造函数,用于在需要创建新函数来添加程序时使用。构造函数必须指定要创建的函数的类型以及函数应该具有哪种类型的链接。FunctionType参数指定函数的形式参数和返回值。同一个FunctionType值可用于创建多个函数。父参数指定定义函数的模块。如果提供了这个参数,函数将自动插入该模块的函数列表中。bool isDeclaration ()
返回函数是否定义了主体。如果函数是“外部的”,那么它就没有主体,因此必须通过与在不同翻译单元中定义的函数链接来解决。Function::iterator
—— 基本块列表迭代器的类型定义
Function::const_iterator
—— const_iterator的类型定义。
begin(), end(), size(), empty()
这些转发方法使访问函数对象的BasicBlock列表的内容变得很容易。Function::BasicBlockListType &getBasicBlockList()
返回BasicBlock列表。当您需要更新列表或执行没有转发方法的复杂操作时,这是必需的。
Function::arg_iterator —— 参数列表iterator的类型定义
Function::const_arg_iterator —— const_iterator的类型定义。
arg_begin(), arg_end(), arg_size(), arg_empty()
这些转发方法使访问函数对象的参数列表的内容变得很容易。Function::ArgumentListType &getArgumentList()
返回参数列表。当您需要更新列表或执行没有转发方法的复杂操作时,这是必需的。BasicBlock &getEntryBlock()
返回函数的入口BasicBlock。因为函数的入口块总是第一个块,所以它返回Function的第一个块。Type *getReturnType()
FunctionType *getFunctionType()
它遍历Function的Type并返回函数的返回类型,或实际函数的FunctionType。SymbolTable *getSymbolTable()
返回指向此函数的SymbolTable的指针。
9.9 GlobalVariable类
#include “llvm/IR/GlobalVariable.h”
header source: GlobalVariable.h
doxygen info: GlobalVariable Class
Superclasses: GlobalValue, Constant, User, Value
全局变量用GlobalVariable
类表示。与函数一样,GlobalVariable
也是GlobalValue
的子类,因此总是由它们的地址引用(全局值必须保存在内存中,所以它们的“name”指的是它们的常量地址)。有关更多信息,请参见GlobalValue。全局变量可能有一个初值(它必须是一个Constant),如果它们有一个initializer,它们可能被标记为“常量”本身(表明它们的内容在运行时从不更改)。
9.9.1 GlobalVariable类的重要Public成员
GlobalVariable(const Type *Ty, bool isConstant, LinkageTypes &Linkage, Constant *Initializer = 0, const std::string &Name = "", Module* Parent = 0)
创建指定类型的一个新全局变量。如果isConstant
为真,那么全局变量将被标记为程序不变。Linkage
参数指定变量的linkage类型(internal, external, weak, linkonce, appending)。如果linkage是InternalLinkage、WeakAnyLinkage、WeakODRLinkage、LinkOnceAnyLinkage或LinkOnceODRLinkage,则生成的全局变量将具有internal linkage。AppendingLinkage将变量的所有实例(在不同的转换单元中)连接到一个变量中,但只适用于数组。有关linkage类型的详细信息,请参阅LLVM语言参考。也可以为全局变量指定一个初始化器、一个名称和要放入变量的模块。bool isConstant() const
如果这是一个已知不能在运行时修改的全局变量,则返回true。bool hasInitializer()
如果这个GlobalVariable有一个初始化器,则返回true。Constant *getInitializer()
返回一个GlobalVariable的初始值。如果没有初始化器,则调用此方法是不合法的。
9.10 BasicBlock类
#include “llvm/IR/BasicBlock.h”
header source: BasicBlock.h
doxygen info: BasicBlock Class
Superclass: Value
该类表示代码的单个入口和单个出口部分,编译器社区通常将其称为基本块。BasicBlock
类维护一个Instructions列表,这些指令构成了块的主体。与语言定义匹配,此指令列表的最后一个元素始终是一个终止符指令。
除了跟踪组成块的指令列表外,BasicBlock类还跟踪它所嵌入的Function。
注意,BasicBlocks本身是Values,因为它们由branches之类的指令引用,所以可以放在switch表中。BasicBlocks有类型label。
9.10.1 BasicBlock类的重要Public成员
BasicBlock(const std::string &Name = "", Function *Parent = 0)
BasicBlock
构造函数用于创建用于插入函数的新基本块。构造函数可选地接受新块的一个名称和将其插入其中的一个Function。如果指定了Parent
参数,则在指定Function的末尾自动插入新的BasicBlock;如果没有指定,则必须手动将BasicBlock插入Function。BasicBlock::iterator
—— 指令列表iterator的类型定义
BasicBlock::const_iterator
—— const_iterator的类型定义。
用于访问指令列表的begin(), end(), front(), back(), size(), empty()
STL样式函数。
这些方法和typedefs
是转发函数,它们具有与相同名称的标准库方法相同的语义。这些方法以易于操作的方式公开基本块的底层指令列表。要获得完整的容器操作(包括更新列表的操作),必须使用getInstList()
方法。BasicBlock::InstListType &getInstList()
此方法用于访问实际包含指令的底层容器。当BasicBlock类中没有要执行的操作的转发函数时,必须使用此方法。因为没有用于“更新”操作的转发函数,所以如果想更新BasicBlock的内容,就需要使用这个函数。Function *getParent()
返回一个指针,它指向这个块所嵌套的Function,或者返回一个空指针(如果它是无家可归的)。Instruction *getTerminator()
返回一个指向出现在BasicBlock末尾的终止符指令的指针。如果没有终止符指令,或者如果块中的最后一条指令不是终止符,则返回一个空指针。
9.11 Argument类
这个Value
的子类为函数的传入形式参数定义接口。一个函数维护其一个形式参数的列表。一个参数有一个指向父Function的指针。