来源:https://llvm.org/docs/ProgrammersManual.html
1. Introduction
本文档的第一部分描述了在 LLVM 基础架构中工作时需要了解的一般信息,第二部分描述了核心 LLVM 类。将来,本手册将使用描述如何使用扩展库的信息进行扩展,例如支配信息、CFG 遍历例程和有用的实用程序,如 InstVisitor
(doxygen) 模板。
2. 基本信息
2.1 C++标准模板库
LLVM 大量使用了 C++ 标准模板库 (STL),这可能比您习惯或以前看到的要多得多。 正因为如此,您可能希望对所使用的技术和库的功能进行一些背景阅读。 有很多很好的页面讨论了 STL,并且您可以获得几本关于该主题的书籍,因此在本文档中将不讨论。
2.2 其他可用参考
http://www.fortran-2000.com/ArnaudRecipes/sharedlib.html
3 Important and useful LLVM APIs
在这里,我们重点介绍一些在编写Transformations时有用且值得了解的 LLVM API。
3.1 The isa<>, cast<> and dyn_cast<> templates
LLVM 源代码库广泛使用了自定义形式的 RTTI。 这些模板与 C++ 的dynamic_cast<>
运算符有许多相似之处,但它们避开了一些缺点(主要是因为 dynamic_cast<> 仅适用于具有 v-table 的类)。 所有这些模板都在llvm/Support/Casting.h
(doxygen) 文件中定义(注意:您很少需要直接引用此文件)。
-
isa<>
isa<>
运算符的工作方式与 Java “instanceof”运算符完全相同。 它根据引用(reference)或指针(pointer)是否指向指定类的实例,返回真或假。 这对于各种类型的约束检查非常有用(下面的示例)。 -
cast<>
cast<>
操作符是一个“检查转换”(checking cast)操作。 它将指针或引用从基类转换为派生类,如果它不是真正正确类型的实例,则会导致assertion failure。 因此,只有在具备一些信息,能够确保实例属于正确类型的情况下使用。 isa<> 和 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());
}
(注意:您不应该使用 isa<> 测试后跟 cast<>,因为会使用 dyn_cast<> 运算符)
assertion failure: In computer programming, an assertion is a statement that a predicate (Boolean-valued function, a true–false expression) is expected to always be true at that point in the code. If an assertion evaluates to false at run time, an assertion failure results, which typically causes the program to crash, or to throw an assertion exception.
- dyn_cast<>
该操作符是一个“检查转换”操作。 它检查操作数是否为指定类型,如果是,则返回指向它的指针(该操作不适用于引用)。 如果操作数的类型不正确,则返回空指针。 因此,它的工作原理与 C++ 中的dynamic_cast<>
运算符非常相似,并且应该在相同的情况下使用。 通常,dyn_cast<>
运算符用于 if 语句或其他一些流控制语句,如下所示:
if (auto *AI = dyn_cast<AllocationInst>(Val)) {
// ...
}
这种形式的 if 语句有效地将对 isa<> 的调用和对 cast<> 的调用组合成一个语句,非常方便。
注意:dyn_cast<>
运算符,类似 C++ 的 dynamic_cast<>
或 Java 的 instanceof
运算符,可能会被滥用。尤其,不应用于大量链接的 if / then / else 块中来检查多种不同的类变体。 这种情况下,使用 InstVisitor Class
直接分派指令类型会更清洁、更高效。
-
isa_and_nonnull<>
该运算符的工作方式与 isa<> 运算符类似,只是它允许空指针作为参数(然后返回false
),想要将多个空检查合并为一个时很有用。 -
cast_or_null<>
该运算符的工作方式与 cast<> 运算符类似,只是它允许空指针作为参数(然后传参),用于将多个空检查合并为一个。 -
dyn_cast_or_null<>
该操作符与 dyn_cast<> 操作符用法类似,除了它允许空指针作为参数(然后传参),在将多个空检查合并为一个时很有用。
这五个模板可以与任何类一起使用,无论它们是否具有 v-table
。 如果您想添加对这些模板的支持,请参阅文档How to set up LLVM-style RTTI for your class hierarchy
3.2 Passing strings (the StringRef and Twine classes)
LLVM 通常不做太多字符串操作,但有几个重要的 API 来处理字符串。 两个重要的例子是 Value Class
——它包含指令、函数等的名称——以及在 LLVM 和 Clang 中广泛使用的 StringMap
类。
这些是通用类,要求能接收可能嵌入空字符的串。 因此,它们不能简单地采用 const char *
,而采用 const std::string&
,要求客户端执行通常不必要的堆分配。 相反,许多 LLVM API 使用 StringRef
或 const Twine&
来有效地传递字符串。
3.2.1 The StringRef class
StringRef
数据类型表示对常量字符串(字符数组和长度)的引用,并支持 std::string
上可用的常见操作,但不需要堆分配。
它可以使用 C 风格的以空字符结尾的字符串 std::string
隐式构造,也可以使用字符指针和长度显式构造。 例如,StringMap find
函数声明为:
iterator find(StringRef Key);
可以使用以下任何一种来调用它:
Map.find("foo"); // Lookup "foo"
Map.find(std::string("bar")); // Lookup "bar"
Map.find(StringRef("\0baz", 4)); // Lookup "\0baz"
类似地,需要返回字符串的 API 可能会返回一个 StringRef
实例,该实例可以直接使用,也可以使用 str
成员函数转换为 std::string
。 请参阅 llvm/ADT/StringRef.h
(doxygen) 。
StringRef
类很少被直接使用,因为它包含指向外部内存的指针,存储该类的实例通常不安全(除非确保外部存储不会被释放)。 StringRef 在 LLVM 中小且无处不在,它应该始终按值传递。
3.2.2 The Twine class
Twine
类是 API 接受串联字符串的一种有效方式。 例如,一个常见的 LLVM 范式是根据另一条指令的名称来命名一条指令,并带有后缀,例如:
New = CmpInst::Create(..., SO->getName() + ".cmp");
Twine
类实际上是一个轻量级rope(一种数据结构),它指向临时(堆栈分配的)对象。 Twines 可以隐式构造为应用于字符串(即,C 字符串、std::string 或 StringRef)的加号运算符的结果。 twine 将字符串的实际连接延迟到实际需要时,此时它可以有效地直接呈现到字符数组中。 这避免了构建字符串连接的临时结果所涉及的不必要的堆分配。 有关更多信息,请参见 llvm/ADT/Twine.h
(doxygen) 和后文。
与 StringRef
一样,Twine
对象指向外部内存,几乎不应该直接存储或提及。 它们仅用于定义能够有效接受连接字符串的函数时。
3.3 Formatting strings (the formatv function)
虽然 LLVM 不必要做很多字符串操作和解析,但它需要做许多字符串格式化。 从诊断消息到llvm-readobj
等 llvm 工具输出,再到打印详细的反汇编列表和 LLDB 运行日志,对字符串格式化的需求无处不在。
formatv
在本质上与 printf
相似,但使用了大量从 Python 和 C# 借用的不同语法。 与 printf 不同,它在编译时推导出要格式化的类型,因此它不需要格式说明符,例如 %d。 这减少了尝试构建可移植格式字符串的mental overhead(精神开销?),尤其是对于特定平台的类型,如 size_t
或指针
类型。 与 printf 和 Python 不同的是,如果 LLVM 不知道如何格式化类型,它会编译失败。 这两个属性确保该函数比 printf 系列函数等传统格式化方法更安全、更易于使用。
3.3.1 simple formating 简单格式化
对 formatv
的调用涉及由 0 个或多个替换序列(replacement sequences) 组成的单个格式化字符串(format string),后跟一个可变长度的替换值(replacement values) 列表。替换序列是 {N[[,align]:style]}
形式的字符串。
N
是指替换值列表中参数的从 0 开始的索引。注意:这意味着可以以任意顺序多次引用相同的参数,可能使用不同的样式和/或对齐选项。
align
是一个可选字符串,用于指定将值格式化至的字段宽度,以及字段内值的对齐方式。它被指定为可选的对齐样式(alignment style),后跟正整数字段宽度(field width)。对齐方式可以是字符之一 -
(左对齐)、=
(居中对齐)或 +
(右对齐)。默认为右对齐。
style
是一个可选字符串,由控制值格式的特定类型组成。例如,要将浮点值格式化为百分比,您可以使用样式选项 P
。
3.3.2 Custom formatting 自定义格式化
有两种方法可以自定义类型的格式化行为:
- 为您的类型
T
提供一种模板特殊化(template specialization)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<T>
继承的格式适配器(format adapter)。
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
Examples
下面旨在提供一组不完整的示例来演示 formatv 的用法。 通过阅读 doxygen 文档或查看单元测试套件可以找到更多信息。
std::string S;
// Simple formatting of basic types and implicit string conversion.
S = formatv("{0} ({1:P})", 7, 0.35); // S == "7 (35.00%)"
// Out-of-order referencing and multi-referencing
outs() << formatv("{0} {2} {1} {0}", 1, "test", 3); // prints "1 3 test 1"
// Left, right, and center alignment
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";
// Custom styles
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"