Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
2
2022fall-Compiler_CMinus
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Metrics
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
李晓奇
2022fall-Compiler_CMinus
Commits
f9071a38
Commit
f9071a38
authored
Nov 22, 2022
by
李晓奇
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
finish report
parent
b878d214
Changes
6
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
208 additions
and
12 deletions
+208
-12
Documentations/4.1-ssa/ssa.pdf
Documentations/4.1-ssa/ssa.pdf
+0
-0
Reports/.gitignore
Reports/.gitignore
+1
-0
Reports/4.1-ssa/report.md
Reports/4.1-ssa/report.md
+198
-7
include/optimization/Dominators.h
include/optimization/Dominators.h
+1
-1
src/cminusfc/cminusfc.cpp
src/cminusfc/cminusfc.cpp
+0
-4
src/optimization/Mem2Reg.cpp
src/optimization/Mem2Reg.cpp
+8
-0
No files found.
Documentations/4.1-ssa/ssa.pdf
View file @
f9071a38
No preview for this file type
Reports/.gitignore
View file @
f9071a38
1-parser/*.pdf
2-ir-gen-warmup/*.pdf
3-ir-gen/*.pdf
4.1-ssa/*.pdf
Reports/4.1-ssa/report.md
View file @
f9071a38
...
...
@@ -2,18 +2,209 @@
## 实验要求
请按照自己的理解,写明本次实验需要干什么
4.
1给出了一个减少内存分配和访问的优化Mem2Reg,通过学习这个示例,我们可以:
1.
看到一种优化方案的很多细节:如何使用支配信息、如何使用
`phi`
函数、如何重命名(实际上是裁剪冗余代码)……
2.
了解实验框架的实现原理:如基本块的CFG信息、指令类型和值类型的互相转换细节、如何注册优化……为下一次实验的优化(我相信下一次实验是要自己实现什么优化)做好准备。
## 思考题
### Mem2reg
1.
...
2.
...
3.
...
1.
请简述概念:支配性、严格支配性、直接支配性、支配边界。
-
支配性:给定CFG中,假设初始块是B~0~,对于一个基本块B~j~,如果基本块B~i~存在于B~0~到B~j~的所有路径上时,则称B~i~支配B~j~。根据定义,其中B~0~和B~j~一定支配B~j~
-
严格支配性:如果B~i~支配B~j~,且B~i~不是B~j~,则B~i~严格支配B~j~
-
直接支配:在严格支配基本块B~j~的块集合中,距离B~j~距离最近的块就是B~j~的直接支配节点,称作IDOM(B~j~)
-
支配边界:如果两个节点B~i~和B~j~满足以下两个条件:
-
B~i~支配B~j~的一个前驱节点
-
B~i~不严格支配B~j~
则认为符合条件的B~j~的集合是B~i~的支配边界,记作DF(B~i~)
2.
`phi`
节点是SSA的关键特征,请简述
`phi`
节点的概念,以及引入
`phi`
节点的理由。
`phi`
节点是构造SSA的技术产物之一。
将程序转化为SSA的过程中,会存在一个问题:在CFG的汇合点,一个变量可能对应不同路径的多个定值,由于SSA要求变量只能被赋值至多一次,不同路径上的定值会对应不同的变量,而在汇合点之后使用变量,就需要决策使用哪个定值。
这件事不能静态确定,解决方案是增加一个
`phi`
函数的赋值,使
`phi`
函数的参数是可能的定值,
`phi`
函数的结果是一个新变量。
`phi`
函数将动态决定使用哪个定值作为新变量的定值,而这个新变量作为汇合点之后的变量定值可以直接使用。
3.
观察下面给出的
`cminus`
程序对应的 LLVM IR,与
**开启**
`Mem2Reg`
生成的LLVM IR对比,每条
`load`
,
`store`
指令发生了变化吗?变化或者没变化的原因是什么?请分类解释。
因为阅读代码后发现,优化是针对函数进行的,所以我们区分函数来观察,先观察函数
`func`
:
```
c
int
func
(
int
x
){
if
(
x
>
0
){
x
=
0
;
}
return
x
;
}
```
使用到的变量就只有int型参数
`x`
,不需要任何分配和
`load`
/
`store`
,所以都被优化掉。
再来看函数
`main`
:
```
cpp
int
main
(
void
){
int
arr
[
10
];
int
b
;
globVar
=
1
;
arr
[
5
]
=
999
;
b
=
2333
;
func
(
b
);
func
(
globVar
);
return
0
;
}
```
变量
`b`
是int型,可以删去,所以被优化掉了。
`arr`
是数组,
`globVar`
是全局变量,所以不会被优化掉,因此优化后的中间代码还保存着关于这两个变量的
`store`
和
`load`
。
4.
指出放置phi节点的代码,并解释是如何使用支配树的信息的。(需要给出代码中的成员变量或成员函数名称)
插入
`phi`
节点的代码在
`src/optimization/Mem2Reg.cpp`
中定义的函数
`void Mem2Reg::generate_phi()`
中。
这个函数将针对当前指定的
**函数**
,遍历其所有
**基本块**
,找到作为
`store`
指令左值的所有
**变量集合**
,同时维护
**这些变量的定值(即store指令)出现的基本块**
。
然后会遍历每个变量,利用
**支配树**
找到变量定值所在基本块的边界集合,在这些支配边界首部插入
`phi`
函数,更新
`work_list`
和基本块的
`phi`
函数信息
`bb_has_var_phi`
。
```
cpp
auto
phi
=
PhiInst
::
create_phi
(
var
->
get_type
()
->
get_pointer_element_type
(),
bb_dominance_frontier_bb
);
phi
->
set_lval
(
var
);
bb_dominance_frontier_bb
->
add_instr_begin
(
phi
);
```
计算活跃变量集合时,算法不计入全局变量和数组内容。按照我的理解,这是因为Mem2Reg算法的目标是将局部非数组变量的(int、float)的内存分配移除,期待用寄存器代之,而全局变量和数组不能满足寄存器分配的性质,所以不对这两种类型做处理。
5.
算法是如何选择
`value`
(变量最新的值)来替换
`load`
指令的?(描述清楚对应变量与维护该变量的位置)
替换
`load`
指令的代码在文件
`src/optimization/Mem2Reg.cpp`
定义的函数
`void Mem2Reg::re_name(BasicBlock *bb)`
中。
截取代码如下:
```
cpp
if
(
instr
->
is_load
())
{
auto
l_val
=
static_cast
<
LoadInst
*>
(
instr
)
->
get_lval
();
if
(
!
IS_GLOBAL_VARIABLE
(
l_val
)
&&
!
IS_GEP_INSTR
(
l_val
))
{
if
(
var_val_stack
.
find
(
l_val
)
!=
var_val_stack
.
end
())
{
// 此处指令替换会维护 UD 链与 DU 链
instr
->
replace_all_use_with
(
var_val_stack
[
l_val
].
back
());
wait_delete
.
push_back
(
instr
);
}
}
}
```
算法在遇
`load`
指令时,会检查源地址数据的最新定值是否已经被保存在栈中,具体是查找这个变量对应的栈
`var_val_stack[l_val]`
是否存在,如果存在则会替换掉这条
`load`
指令,使用栈顶定值
`var_val_stack[l_val].back()`
代替其结果。
而针对这个栈的维护还是在
`rename`
函数中,主要做一下几点维护:
-
`phi`
函数和
`store`
指令对一个变量(局部非数组)的最新定值会被压入栈顶。
-
离开
`re_name`
时恢复栈至进入函数的状态。
### 代码阅读总结
此次实验有什么收获
#### UD链与DU链
-
UD链在
`User`
类中维护,内部变量为
`operands_`
,主要是一个使用者(指令、全局变量等)使用了哪些定值。
-
DU链在
`Value`
类中维护,内部变量是
`use_list_`
,表示使用这个
`Value`
变量的对象集合。
在阅读Mem2Reg时,替换load处一个一知半解的函数
`replace_all_use_with`
:
```
cpp
void
Value
::
replace_all_use_with
(
Value
*
new_val
)
{
for
(
auto
use
:
use_list_
)
{
auto
val
=
dynamic_cast
<
User
*>
(
use
.
val_
);
assert
(
val
&&
"new_val is not a user"
);
val
->
set_operand
(
use
.
arg_no_
,
new_val
);
}
use_list_
.
clear
();
}
```
根据这段代码,搞清楚了这个函数的逻辑:针对一个
`Value*`
变量,将使用到它的位置都替换为另一个
`Value*`
变量
`new_val`
,不仅要将自己的DU链清空,还要将原来在DU链上的所用定值使用都更换为
`new_val`
。
回到当初不懂的地方:
```
cpp
if
(
instr
->
is_load
())
{
auto
l_val
=
static_cast
<
LoadInst
*>
(
instr
)
->
get_lval
();
if
(
!
IS_GLOBAL_VARIABLE
(
l_val
)
&&
!
IS_GEP_INSTR
(
l_val
))
{
if
(
var_val_stack
.
find
(
l_val
)
!=
var_val_stack
.
end
())
{
// 此处指令替换会维护 UD 链与 DU 链
instr
->
replace_all_use_with
(
var_val_stack
[
l_val
].
back
());
wait_delete
.
push_back
(
instr
);
}
}
}
```
这就是在
`load`
指令可以被替换的时候,
`load`
指令作为一个
`Value*`
变量,需要调用其成员函数
`replace_all_use_with`
,来将其所有使用替换成栈顶的定值。
#### Mem2Reg
-
`run`
:针对每个函数的产生phi指令
`generate_phi()`
、重命名
`re_name()`
和移除冗余alloca指令
`remove_alloca()`
。
-
`generate_phi()`
1.
计算跨基本块的活跃变量集合
`global_live_var_name`
,并维护每个活跃变量出现的基本块集合
`live_var_2blocks`
依据
`store`
指令即定值产生
这里活跃变量是针对alloca分配过空间的变量,不可以是全局or
`GEP`
产生的地址。
2.
遍历活跃变量集合,在每个变量定值所在基本块的支配边界处产生
`phi`
函数。
代码中存在若干细节:
-
使用结构
`map<std::pair<BasicBlock *, Value *>, bool>`
维护信息:基本块针对某一变量是否存在
`phi`
函数。
-
`work_list`
在循环中是变化的,所以使用下标而非auto索引。
-
`re_name(bb)`
因为原本就是SSA,所以不需要counter,需要的是裁减冗余代码,修改操作数。资料中的stack使用
`var_val_stack`
实现,一个全局活跃变量的栈顶表示编译过程中的最新定值。
1.
将bb的
`phi`
函数指令压栈,作为其结果的定值。
2.
针对store指令,更新栈顶,即压入右值。对于load指令,将load的结果引用都替换为栈顶定值(如果有的话)。如果上述操作成果,store、load均可删去。
3.
填充后继节点的
`phi`
指令。
4.
在支配树上递归重命名。
5.
恢复栈状态至初始,根据store指令和phi指令的结果变量弹出。
6.
去除第2步的冗余指令。
-
`remove_alloca()`
这里应该是有一个断言:经过前边的一系列处理,基本块内部的int、float的分配(alloca)指令都是冗余的,应该直接删去。
这里删去的alloca与
`re_name()`
中的有什么区别
### 实验反馈 (可选 不会评分)
对本次实验的建议
对同学整体来说,鼓励大家读代码很好,因为有的同学前几个实验做不明白就是因为不看框架代码。
对我自己来说,我还是有点读不清楚,有的地方似懂非懂,估计下个实验真动手做了就会好一点吧。
## 参考
[
PHI Node总结
](
https://www.jianshu.com/p/497a9d0e1b3e
)
[
llvm PHI Node
](
https://www.llvmpy.org/llvmpy-doc/dev/doc/llvm_concepts.html#ssa-form-and-phi-nodes
)
[
北航的mem2reg教程
](
https://buaa-se-compiling.github.io/miniSysY-tutorial/challenge/mem2reg/help.html
)
include/optimization/Dominators.h
View file @
f9071a38
...
...
@@ -8,7 +8,7 @@
#include <map>
#include <set>
class
Dominators
:
public
Pass
{
class
Dominators
:
public
Pass
{
public:
explicit
Dominators
(
Module
*
m
)
:
Pass
(
m
)
{}
~
Dominators
()
=
default
;
...
...
src/cminusfc/cminusfc.cpp
View file @
f9071a38
...
...
@@ -11,11 +11,7 @@
using
namespace
std
::
literals
::
string_literals
;
void
print_help
(
std
::
string
exe_name
)
{
<<<<<<<
HEAD
std
::
cout
<<
"Usage: "
<<
exe_name
<<
" [ -h | --help ] [ -o <target-file> ] [ -emit-llvm ] <input-file>"
=======
std
::
cout
<<
"Usage: "
<<
exe_name
<<
" [ -h | --help ] [ -o <target-file> ] [ -emit-llvm ] [-mem2reg] <input-file>"
>>>>>>>
2
f5d0fa70297542b1032ca4614f0f426fa7a4e33
<<
std
::
endl
;
}
...
...
src/optimization/Mem2Reg.cpp
View file @
f9071a38
...
...
@@ -21,6 +21,7 @@ void Mem2Reg::run() {
func_
=
&
f
;
if
(
func_
->
get_basic_blocks
().
size
()
>=
1
)
{
// 对应伪代码中 phi 指令插入的阶段
// why no parameter here: func_ is visible in the generate_phi()
generate_phi
();
// 对应伪代码中重命名阶段
re_name
(
func_
->
get_entry_block
());
...
...
@@ -43,6 +44,8 @@ void Mem2Reg::generate_phi() {
if
(
instr
->
is_store
())
{
// store i32 a, i32 *b
// a is r_val, b is l_val
// may be b is from instruction:
// %b = getelementptr ...
auto
r_val
=
static_cast
<
StoreInst
*>
(
instr
)
->
get_rval
();
auto
l_val
=
static_cast
<
StoreInst
*>
(
instr
)
->
get_lval
();
...
...
@@ -59,6 +62,7 @@ void Mem2Reg::generate_phi() {
for
(
auto
var
:
global_live_var_name
)
{
std
::
vector
<
BasicBlock
*>
work_list
;
work_list
.
assign
(
live_var_2blocks
[
var
].
begin
(),
live_var_2blocks
[
var
].
end
());
// cannot use auto &bb: work_list, because of the insertion in the loop
for
(
int
i
=
0
;
i
<
work_list
.
size
();
i
++
)
{
auto
bb
=
work_list
[
i
];
for
(
auto
bb_dominance_frontier_bb
:
dominators_
->
get_dominance_frontier
(
bb
))
{
...
...
@@ -117,6 +121,8 @@ void Mem2Reg::re_name(BasicBlock *bb) {
// 步骤六:为 lval 对应的 phi 指令参数补充完整
for
(
auto
succ_bb
:
bb
->
get_succ_basic_blocks
())
{
for
(
auto
&
instr1
:
succ_bb
->
get_instructions
())
{
// should be:
// if(instr is not phi): break
auto
instr
=
&
instr1
;
if
(
instr
->
is_phi
())
{
auto
l_val
=
static_cast
<
PhiInst
*>
(
instr
)
->
get_lval
();
...
...
@@ -157,6 +163,8 @@ void Mem2Reg::re_name(BasicBlock *bb) {
}
}
// There is an assertion:
// all the alloca for int|float will be no dead after the pass
void
Mem2Reg
::
remove_alloca
()
{
for
(
auto
&
bb1
:
func_
->
get_basic_blocks
())
{
auto
bb
=
&
bb1
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment