Skip to content

Commit 88a7ad5

Browse files
committed
Add intrinsics, attributes and metadatas
1 parent 01ed390 commit 88a7ad5

File tree

3 files changed

+55
-246
lines changed

3 files changed

+55
-246
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# 内置函数、属性和元数据
2+
3+
在LLVM IR中,除了基础的数据表示、控制流之外,还有内置函数、属性和元数据等,能够影响二进制程序生成的功能。
4+
5+
## 内置函数
6+
7+
我们回顾一下,LLVM IR的作用实际上是将编译器前端与后端解耦合。编程语言的前端开发者,负责将输入的编程语言代码进行解析,生成LLVM IR;指令集架构的后端开发者,负责将输入的LLVM IR生成为目标架构的二进制指令。因此,LLVM IR提供了若干非常基础的指令,如`add``br``call`等。这样做的好处在于:
8+
9+
* 对前端开发者而言,这些指令语义足够全,使用方法也和常见高级语言类似。
10+
* 对后端开发者而言,这些指令相对数目比较少,提供的功能也相对较为独立,在大部分常见的指令集中都有类似的指令与其对应。
11+
12+
但是,这样的策略也有其弊端:
13+
14+
* 对前端开发者而言,仍然有部分通用的语义无法被单个指令所涵盖
15+
* 对后端开发者而言,对一些通用指令的优化无法针对LLVM IR指令来做
16+
17+
### `memcpy`
18+
19+
以内存拷贝为例。熟悉AMD64或者AArch64的开发者一定知道,在这些支持向量操作的指令集架构中,大规模的内存拷贝往往是通过向量指令来实现的,Glibc中的`memcpy`就是这样实现的。
20+
21+
但是对于通用编程语言来说,标准库往往不喜欢直接调用libc中的函数,会产生一些不必要的依赖。并且,`memcpy`用向量操作来实现已经是一个非常通用的方案了,所以能不能复用一些逻辑呢?
22+
23+
对于此类,LLVM IR指令过于基础,但是却非常广泛地使用同一套实现逻辑的情况,LLVM IR提供了「[内置函数](https://llvm.org/docs/LangRef.html#intrinsic-functions)」(Intrinsic Functions)功能来解决。
24+
25+
所谓内置函数,我们可以理解成一些可以像普通的LLVM IR函数一样调用的函数,但这些函数不需要开发者自己实现,LLVM的后端开发者提供了这些函数的实现。
26+
27+
例如,LLVM IR提供了[`llvm.memcpy`](https://llvm.org/docs/LangRef.html#llvm-memcpy-intrinsic)内置函数,以提供内存的拷贝操作。前端开发者只需要调用这个函数,就可以实现内存拷贝功能了。
28+
29+
我们熟知的Rust语言,在利用LLVM生成二进制程序时,就是使用的这个函数,可以参考其封装的[`LLVMRustBuildMemCpy`](https://github.com/rust-lang/rust/blob/90c541806f23a127002de5b4038be731ba1458ca/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp#L1448-L1456)与调用者[`memcpy`](https://github.com/rust-lang/rust/blob/90c541806f23a127002de5b4038be731ba1458ca/compiler/rustc_codegen_llvm/src/builder.rs#L871-L896)
30+
31+
### 静态分支预测
32+
33+
LLVM IR提供的内置函数有许多,这里,我们再以静态分支预测为例,介绍一个常见内置函数。
34+
35+
我们在阅读一些大规模项目源码时,例如Linux内核源码、QEMU源码等,往往会注意到大量使用的`likely``unlikely`,如:
36+
37+
```c
38+
if (likely(x > 0)) {
39+
// Do something
40+
}
41+
```
42+
43+
这个`likely`是什么?它是干什么用的?事实上,`likely``unlikely`往往是通过宏定义实现的,它们的作用是静态分支预测。
44+
45+
我们知道,对于C语言等常见的编程语言的`if`语句,在生成二进制程序的时候,我们可以交换它的两个分支的位置。紧接着`cmp`等判断语句的分支,在执行时,不会发生跳转,而另一个分支则需要设置PC寄存器来跳转。这种跳转往往会造成一定程度的性能损耗,这些具体的我在「[在 Apple Silicon Mac 上入门汇编语言](https://github.com/Evian-Zhang/learn-assembly-on-Apple-Silicon-Mac)」中的[编译期分支预测](https://evian-zhang.github.io/learn-assembly-on-Apple-Silicon-Mac/11-跳转.html#编译期分支预测)一节中有详细阐述。总之,我们需要给编译器一些信息,来排布不同的分支布局。
46+
47+
对于Clang来说,这是通过[内置`expect`指令](https://llvm.org/docs/BranchWeightMetadata.html#built-in-expect-instructions)来实现的,也就是说:
48+
49+
```c
50+
#define likely(x) __builtin_expect(!!(x), 1)
51+
#define unlikely(x) __builtin_expect(!!(x), 0)
52+
```
53+
54+
`__builtin_expect`这个内置指令,就会翻译为LLVM IR中的[`llvm.expect`](https://llvm.org/docs/LangRef.html#llvm-expect-intrinsic)内置函数,从而实现了静态分支预测。

src/07-异常处理.md

Lines changed: 0 additions & 245 deletions
This file was deleted.

src/SUMMARY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@
1212
- [控制流](05-控制流/README.md)
1313
- [控制语句](05-控制流/01-控制语句.md)
1414
- [函数](05-控制流/02-函数.md)
15-
- [异常处理](07-异常处理.md)
15+
- [内置函数、属性和元数据](06-内置函数、属性和元数据.md)

0 commit comments

Comments
 (0)