Skip to content

Commit 953063d

Browse files
committed
Add data usage
1 parent 28a2aa2 commit 953063d

File tree

8 files changed

+87
-95
lines changed

8 files changed

+87
-95
lines changed

code/article_03/global_variable_test.ll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33

44
define i32 @main() {
55
ret i32 0
6-
}
6+
}

code/article_03/many_registers_test.ll

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,21 @@ define i32 @main() {
1818
%14 = add i32 1, 2
1919
%15 = add i32 1, 2
2020

21-
store i32 %1, i32* @global_variable
22-
store i32 %2, i32* @global_variable
23-
store i32 %3, i32* @global_variable
24-
store i32 %4, i32* @global_variable
25-
store i32 %5, i32* @global_variable
26-
store i32 %6, i32* @global_variable
27-
store i32 %7, i32* @global_variable
28-
store i32 %8, i32* @global_variable
29-
store i32 %9, i32* @global_variable
30-
store i32 %10, i32* @global_variable
31-
store i32 %11, i32* @global_variable
32-
store i32 %12, i32* @global_variable
33-
store i32 %13, i32* @global_variable
34-
store i32 %14, i32* @global_variable
35-
store i32 %15, i32* @global_variable
21+
store i32 %1, ptr @global_variable
22+
store i32 %2, ptr @global_variable
23+
store i32 %3, ptr @global_variable
24+
store i32 %4, ptr @global_variable
25+
store i32 %5, ptr @global_variable
26+
store i32 %6, ptr @global_variable
27+
store i32 %7, ptr @global_variable
28+
store i32 %8, ptr @global_variable
29+
store i32 %9, ptr @global_variable
30+
store i32 %10, ptr @global_variable
31+
store i32 %11, ptr @global_variable
32+
store i32 %12, ptr @global_variable
33+
store i32 %13, ptr @global_variable
34+
store i32 %14, ptr @global_variable
35+
store i32 %15, ptr @global_variable
3636

3737
ret i32 0
38-
}
38+
}

code/article_03/register_test.ll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
define i32 @main() {
33
%local_variable = add i32 1, 2
44
ret i32 %local_variable
5-
}
5+
}
File renamed without changes.

src/03-02-寄存器和栈.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ function max(register a, register b) {
9696

9797
因此,由这个简单的例子我们可以看出来,如果寄存器的数量足够,并且代码中没有需要操作内存地址的时候,寄存器是足够胜任的,并且更加高效的。
9898

99-
#### 寄存器
99+
## 寄存器
100100

101101
正因为如此,LLVM IR引入了虚拟寄存器的概念。在LLVM IR中,一个函数的局部变量可以是寄存器或者栈上的变量。对于寄存器而言,我们只需要像普通的赋值语句一样操作,但需要注意名字必须以`%`开头:
102102

@@ -125,14 +125,14 @@ main:
125125

126126
确实这个局部变量`%local_variable`变成了寄存器`eax`
127127

128-
关于寄存器,我们还需了解一点。在不同的ABI下,会有一些called-saved register和calling-saved register。简单来说,就是在函数内部,某些寄存器的值不能改变。或者说,在函数返回时,某些寄存器的值要和进入函数前相同。比如,在System V的ABI下,`rbp`, `rbx`, `r12`, `r13`, `r14`, `r15`都需要满足这一条件。由于LLVM IR是面向多平台的,所以我们需要一份代码适用于多种ABI。因此,LLVM IR内部自动帮我们做了这些事。如果我们把所有没有被保留的寄存器都用光了,那么LLVM IR会帮我们把这些被保留的寄存器放在栈上,然后继续使用这些被保留寄存器。当函数退出时,会帮我们自动从栈上获取到相应的值放回寄存器内。
128+
关于寄存器,我们还需了解一点。在不同的ABI下,会有一些callee-saved register和caller-saved register。简单来说,就是在函数内部,某些寄存器的值不能改变。或者说,在函数返回时,某些寄存器的值要和进入函数前相同。比如,在System V的ABI下,`rbp`, `rbx`, `r12`, `r13`, `r14`, `r15`都需要满足这一条件。由于LLVM IR是面向多平台的,所以我们需要一份代码适用于多种ABI。因此,LLVM IR内部自动帮我们做了这些事。如果我们把所有没有被保留的寄存器都用光了,那么LLVM IR会帮我们把这些被保留的寄存器放在栈上,然后继续使用这些被保留寄存器。当函数退出时,会帮我们自动从栈上获取到相应的值放回寄存器内。
129129

130130
那么,如果所有通用寄存器都用光了,该怎么办?LLVM IR会帮我们把剩余的值放在栈上,但是对我们用户而言,实际上都是虚拟寄存器,用户是感觉不到差别的。
131131

132132
因此,我们可以粗略地理解LLVM IR对寄存器的使用:
133133

134-
* 当所需寄存器数量较少时,直接使用called-saved register,即不需要保留的寄存器
135-
* 当called-saved register不够时,将calling-saved register原本的值压栈,然后使用calling-saved register
134+
* 当所需寄存器数量较少时,直接使用callee-saved register,即不需要保留的寄存器
135+
* 当callee-saved register不够时,将caller-saved register原本的值压栈,然后使用caller-saved register
136136
* 当寄存器用光以后,就把多的虚拟寄存器的值压栈
137137

138138
我们可以写一个简单的程序验证。对于x86_64架构下,我们只需要使用15个虚拟寄存器就可以验证这件事。鉴于篇幅,我就不把代码放在文章中了,如果想看详细代码可以去我的GitHub仓库中查看`many_registers_test.ll`。我们将其编译成汇编语言之后,可以看到在函数开头就有
@@ -153,7 +153,7 @@ addl $1, %eax
153153
movl %eax, -4(%rsp)
154154
```
155155

156-
####
156+
##
157157

158158
我们之前说过,当不需要操作地址并且寄存器数量足够时,我们可以直接使用寄存器。而LLVM IR的策略保证了我们可以使用无数的虚拟寄存器。那么,在需要操作地址以及需要可变变量(之后会提到为什么)时,我们就需要使用栈。
159159

@@ -163,4 +163,4 @@ LLVM IR对栈的使用十分简单,直接使用`alloca`指令即可。如:
163163
%local_variable = alloca i32
164164
```
165165

166-
就可以声明一个在栈上的变量了。关于栈上变量的操作,我会在下面提到,目前我们对栈上变量的了解只需这么多。
166+
就可以声明一个在栈上的变量了。关于栈上变量的操作,我会在之后提到,目前我们对栈上变量的了解只需这么多。

src/03-03-数据的使用.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# 数据的使用
2+
3+
在之前的两篇文章中,我们解释了LLVM中是如何对应数据区、寄存器和栈上的数据的。那么,这些数据定义了以后,该如何使用呢?
4+
5+
## 全局变量和栈上变量皆指针
6+
7+
下面,我们就需要讲怎样使用全局变量和栈上的变量。这两种变量实际上是类似的,LLVM IR把它们都看作指针。也就是说,对于全局变量:
8+
9+
```llvm
10+
@global_variable = global i32 0
11+
```
12+
13+
和栈上变量
14+
15+
```llvm
16+
%local_variable = alloca i32
17+
```
18+
19+
这两个变量实际上都是`ptr`指针,指向它们所处的一个`i32`大小的内存区域。所以,我们不能这样:
20+
21+
```llvm
22+
%1 = add i32 1, @global_variable ; Wrong!
23+
```
24+
25+
因为`@global_variable`只是一个指针。
26+
27+
如果要操作这些值,必须使用`load``store`这两个命令。如果我们要获取`@global_variable`的值,就需要
28+
29+
```llvm
30+
%1 = load i32, ptr @global_variable
31+
```
32+
33+
这个指令的意思是,把一个`ptr`指针`@global_variable``i32`类型的值赋给虚拟寄存器`%1`,然后我们就能愉快地
34+
35+
```llvm
36+
%2 = add i32 1, %1
37+
```
38+
39+
这样了。
40+
41+
类似地,如果我们要将值存储到全局变量或栈上变量里,会需要`store`命令:
42+
43+
```llvm
44+
store i32 1, ptr @global_variable
45+
```
46+
47+
这个代表将`i32`类型的值`1`赋给`ptr`类型的全局变量`@global_variable`所指的内存区域中。
48+
49+
## SSA
50+
51+
LLVM IR是一个严格遵守SSA(Static Single Assignment)策略的语言。SSA的要求很简单:每个变量只被赋值一次。也就是说,你不能
52+
53+
```llvm
54+
%1 = add i32 1, 2
55+
%1 = add i32 3, 4
56+
```
57+
58+
`%1`同时赋值两次是不被允许的。
59+
60+
SSA作为一个历史悠久的概念,已经有了相当成熟的相关技术。通过使用SSA,编译器可以进行更好的优化,应用更成熟的算法,得到更好的结果。这里因为个人能力有限,就不再多对SSA进行介绍。我们只需要知道,通过约束每个变量只被赋值一次,可以让LLVM更好地优化。
61+
62+
上面这个例子好做,直接把3加4的结果赋值给一个新的虚拟寄存器就好了。但是,并非所有的情况都这么简单。在一些复杂情况下,将值存储在栈上再取出来,或者使用`phi`指令(见之后控制语句一章),也是一个更好的选择。

src/03-数据表示.md

Lines changed: 0 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -70,74 +70,3 @@ int main() {
7070
* 寄存器中的数据
7171
* 栈上的数据
7272
* 数据区里的数据
73-
74-
## LLVM IR中的数据表示
75-
76-
LLVM IR中,我们需要表示的数据也是以上三种。那么,这三种数据各有什么特点,又需要根据LLVM的特性做出什么样的调整呢?
77-
78-
### 全局变量和栈上变量皆指针
79-
80-
下面,我们就需要讲怎样操作全局变量和栈上的变量。这两种变量实际上是类似的,LLVM IR把它们都看作指针。也就是说,对于全局变量:
81-
82-
```llvm
83-
@global_variable = global i32 0
84-
```
85-
86-
和栈上变量
87-
88-
```llvm
89-
%local_variable = alloca i32
90-
```
91-
92-
这两个变量实际上都是`i32*`类型的指针,指向它们所处的内存区域。所以,我们不能这样:
93-
94-
```llvm
95-
%1 = add i32 1, @global_variable ; Wrong!
96-
```
97-
98-
因为`@global_variable`只是一个指针。
99-
100-
如果要操作这些值,必须使用`load``store`这两个命令。如果我们要获取`@global_variable`的值,就需要
101-
102-
```llvm
103-
%1 = load i32, i32* @global_variable
104-
```
105-
106-
这个指令的意思是,把一个`i32*`类型的指针`@global_variable``i32`类型的值赋给虚拟寄存器`%1`,然后我们就能愉快地
107-
108-
```llvm
109-
%2 = add i32 1, %1
110-
```
111-
112-
这样了。
113-
114-
类似地,如果我们要将值存储到全局变量或栈上变量里,会需要`store`命令:
115-
116-
```llvm
117-
store i32 1, i32* @global_variable
118-
```
119-
120-
这个代表将`i32`类型的值`1`赋给`i32*`类型的全局变量`@global_variable`所指的内存区域中。
121-
122-
### SSA
123-
124-
LLVM IR是一个严格遵守SSA(Static Single Assignment)策略的语言。SSA的要求很简单:每个变量只被赋值一次。也就是说,你不能
125-
126-
```llvm
127-
%1 = add i32 1, 2
128-
%1 = add i32 3, 4
129-
```
130-
131-
`%1`同时赋值两次是不被允许的。
132-
133-
那么,我们应该怎样实现可变变量呢?很简单,把可变变量放到全局变量或者栈内变量里,虚拟寄存器只存储不可变的变量。比如说,我想实现上面的功能,把两次运算结果储存到同一个变量内:
134-
135-
```llvm
136-
%stack_variable = alloca i32
137-
%1 = add i32 1, 2
138-
store i32 %1, i32* %stack_variable
139-
%2 = add i32 3, 4
140-
store i32 %2, i32* %stack_variable
141-
```
142-
143-
我们同样遵守了SSA,而且也满足了可变变量的需求。此外,虽然LLVM IR上看上去很复杂,LLVM后端也会帮我们优化到比较简单的形式,不会因为SSA而降低性能。

src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- [数据表示](03-数据表示.md)
88
- [数据区与符号表](03-01-数据区与符号表.md)
99
- [寄存器和栈](03-02-寄存器和栈.md)
10+
- [数据的使用](03-03-数据的使用.md)
1011
- [类型系统](04-类型系统.md)
1112
- [控制语句](05-控制语句.md)
1213
- [函数](06-函数.md)

0 commit comments

Comments
 (0)