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
92ddf834
Commit
92ddf834
authored
Dec 08, 2022
by
李晓奇
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
can pass loop3
parent
0f5a162d
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
148 additions
and
72 deletions
+148
-72
Documentations/4.2-gvn/README.md
Documentations/4.2-gvn/README.md
+9
-0
Reports/4.2-gvn/figures/index_set.png
Reports/4.2-gvn/figures/index_set.png
+0
-0
Reports/4.2-gvn/figures/loop3.png
Reports/4.2-gvn/figures/loop3.png
+0
-0
Reports/4.2-gvn/figures/value_phi_func.png
Reports/4.2-gvn/figures/value_phi_func.png
+0
-0
Reports/4.2-gvn/report.md
Reports/4.2-gvn/report.md
+135
-68
gvn.json
gvn.json
+3
-0
src/optimization/GVN.cpp
src/optimization/GVN.cpp
+1
-4
No files found.
Documentations/4.2-gvn/README.md
View file @
92ddf834
# Lab4 实验文档
-
[
Lab4 实验文档
](
#lab4-实验文档
)
-
[
0. 前言
](
#0-前言
)
-
[
1. GVN 基础知识
](
#1-gvn-基础知识
)
-
[
1.1 GVN 简介
](
#11-gvn-简介
)
-
[
1.2 GVN 相关概念
](
#12-gvn-相关概念
)
-
[
1. IR 假设
](
#1-ir-假设
)
...
...
@@ -14,8 +17,11 @@
-
[
7. Join 操作
](
#7-join-操作
)
-
[
8. 变量等价与表达式等价
](
#8-变量等价与表达式等价
)
-
[
9. Transfer Function
](
#9-transfer-function
)
-
[
2. GVN 算法(论文中提供的伪代码)
](
#2-gvn-算法论文中提供的伪代码
)
-
[
3. 实验内容
](
#3-实验内容
)
-
[
3.1 GVN pass 实现内容要求
](
#31-gvn-pass-实现内容要求
)
-
[
3.2 GVN 辅助类
](
#32-gvn-辅助类
)
-
[
3.3 注册及运行 GVN Pass
](
#33-注册及运行-gvn-pass
)
...
...
@@ -23,8 +29,11 @@
-
[
运行 Pass
](
#运行-pass
)
-
[
3.3 自动测试
](
#33-自动测试
)
-
[
3.4 Tips
](
#34-tips
)
-
[
4. 提交要求
](
#4-提交要求
)
-
[
目录结构
](
#目录结构
)
-
[
提交要求和评分标准
](
#提交要求和评分标准
)
## 0. 前言
...
...
Reports/4.2-gvn/figures/index_set.png
0 → 100644
View file @
92ddf834
24.2 KB
Reports/4.2-gvn/figures/loop3.png
0 → 100644
View file @
92ddf834
150 KB
Reports/4.2-gvn/figures/value_phi_func.png
0 → 100644
View file @
92ddf834
93.1 KB
Reports/4.2-gvn/report.md
View file @
92ddf834
...
...
@@ -66,6 +66,60 @@ detectEquivalences(G)
改完这些,终于能跑通第一版本了……
### 对于函数`Intersect(Ci, Cj )`
伪代码中,一个等价类是一个集合,求交后,v~k~是自然而然的在或不在新集合中,但是实现中并不这么简单,如何对这样的两个结构体进行逻辑上的取交:
```cpp
struct CongruenceClass {
size_t index_;
Value *leader_;
std::shared_ptr<GVNExpression::Expression> value_expr_;
std::shared_ptr<GVNExpression::PhiExpression> value_phi_;
std::set<Value *> members_;
}
```
- 首先是Intersect的伪代码描述中,下边这句怎么在实现中体现?
> C~k~ does not have value number
解决:index不同的自然就交不出v~i~,相同的时候才有v~i~即value number.
隐患:transferFunction要对v~i~有继承性,即不能每次涉及一个等价类都新开一个value number(这个在后边的值编号维护中已经解决)
- 根据上一条讨论,得出一个观点:在伪代码中,一个等价类集合会出现的内容,在实现中的对应如下:
- 普通变量:`members_`
- 值表达式:`value_expr_`
- phi函数:`value_phi_`
- 值编号v~i~:`index`
因此,伪代码中对集合的求交,对应到实现上,就是对以上四个域求交。
- 额外设计:赋予**精准的**值编号
在我的设计中,每条指令都有一个潜在的值编号,如果要为这条指令新建等价类,就使用这个值编号。所以需要`Intersect`确定新的值编号时,应该选用靠前的以为后边指令腾出编号空间。
### 对于函数`valuePhiFunc(ve, P)`
- 输入就一个分区,实际上要用到两个前驱的分区,怎么处理?
传入基本块做参数。
- 二元运算的顺序颠倒怎么办?
> 如phi(x1,y1)+phi(x2,y2)和phi(x1,y1)+phi(y2,x2)
>
> 对于第二种,单纯寻找x1+y2和y1+x2肯定不合逻辑
论文中有详细的版本:

### 等价类设计
> ```cpp
...
...
@@ -123,6 +177,23 @@ detectEquivalences(G)
- 其他产生赋值的指令,也会由`transferFunction`传递给`ValueExpr`,这些指令是:`load`、`alloca`和返回值非空的`call`。
这里设计一种值表达式类型:`UniqueExpression`,这个表达式和任何其他都不等价,表现为`equiv`直接返回false。
> 看到有同学在的表达式打印都是`add v1 v2`这种,而我是`add %op1=... %op2=...`,好整洁好漂亮,心动了,就搞他。
>
> 因为等价类树的叶子节点就两个:`constant`和这个`Unique`
>
> 常量打印对应的数字即可,而我想实现Unique打印v~i~的效果,所以在其中保存了等价类的值编号`index`,看起来值编号是等价类的属性,似乎不应该和表达式掺和,但是正如其名:UNIQUE,不应该与其他相同的,所以他一定是单独一个等价类,不受其他影响,index是恒定的。
>
> 改好后,打印有如下效果:
>
> ```
> [INFO] (GVN.cpp:820L run)
> index: 9
> leader: %op13 = call i32 @input()
> value phi: nullptr
> value expr: v9
> members: {%op13 = call i32 @input(); }
> ```
#### 若干需要梳理的问题
...
...
@@ -143,8 +214,14 @@ detectEquivalences(G)
这个问题源于`transferFunction`的一句话:
> if ve or vpf is in a class Ci in POUTs
可以的,等价类中的元素都有唯一的表达式,其他等价类即使形式相同,操作数也不会相同,所以等价类和表达式其实是一一对应的关系。
(这也解释了UNIQUE中传入index的合理性)
- 代表元如何选取?什么时候选取?
目前只对常量传播的Constant特别设置了主元,负责设置为`members_`的第一个元素。
- 怎么判断等价类相等
...
...
@@ -152,12 +229,6 @@ detectEquivalences(G)
从这个例子中可以发现,迭代收敛时,是pout中对应分区的`members_`不变,值编号还是会变化的,所以判断等价类相等,应该比较`members_`集合。
- 什么时候给新的值编号?
1. 执行转移函数发现没有可以归属的等价类时
2. 求交巴拉巴拉还没搞清楚那里
- 等价类为空的判定,可以用`members_`做判断吗?
我认为是可以的,存在情况:两个基本块汇合时,交出一个集合,它的members为空,但是ve不空:
...
...
@@ -180,88 +251,84 @@ detectEquivalences(G)
因为最终反映到IR上,我们直接关心的都是`members_`中的成员,所以用`members_`的空代表等价类的空我觉得是合适的。
| 类型 | 处理 |
| ---------------- | ------------------------- |
| 二元运算 | 依伪代码 |
| 没有返回值的 | 不处理 |
| phi函数 | 本块中的不管,后继块的做考虑 |
| 函数调用 | 有返回值的考虑等价类,**注意后边纯函数的处理** |
| 产生指针(GEP、alloca) | 先为返回值单独新建等价类 |
| 类型转换及0扩展 | 单独新建等价类 |
| 比较 | 根据op和操作数递归判断,应该和二元运算类似 |
| load | 单独新建等价类 |
### 值编号的维护
###
对于函数`Intersect(Ci, Cj )`
###
# 值编号的目的
伪代码中,一个等价类是一个集合,求交后,v~k~是自然而然的在或不在新集合中,但是实现中并不这么简单,如何对这样的两个结构体进行逻辑上的取交:
> 编号即`index_`不能一直递增,`loop3.ll`检测了这一点。
> 
> 关注`%op12`和基本块`label17`:
>
> 第二次迭代过程中,`label17`的两个前驱作交集,对应的是两个分区分别是:
>
> - `label11`的pout,第二次迭代结果
>
> - `label24`的pout,第一次迭代结果
>
> 如果每次新建等价类都使index递增,注意第二次经过`label11`新建出来的等价类编号和第一次也即`label24`那个不一样,这时候麻烦就来了:`INTERSECT`函数会交出一个`phi`函数。
>
> 关键在于:index不应该递增,对于`%op12`,第二次迭代明明是同一个位置的定值,应该享有相同的值编号。
一个目标清晰起来:我要保证,不同迭代中,同一位置生成的等价类要享有相同的值编号,以标示同一个等价类。这里的同一个不是指完全相同,举例如下:
```cpp
struct CongruenceClass {
size_t index_;
Value *leader_;
std::shared_ptr<GVNExpression::Expression> value_expr_;
std::shared_ptr<GVNExpression::PhiExpression> value_phi_;
std::set<Value *> members_;
}
```
// partition: {}
x=1
// partition: {v1, 1, x}
y = 1
// partition: {v1, 1, x, y}
```
- 首先是Intersect的伪代码描述中,下边这句怎么在实现中体现?
> C~k~ does not have value number
解决:index不同的自然就交不出v~i~,相同的时候才有v~i~即value number.
隐患:transferFunction要对v~i~有继承性,即不能每次涉及一个等价类都新开一个value number
后两个分区具有同一值编号`
v1
`,但是内部包含了不同的元素。
- 根据上一条讨论,得出一个观点:在伪代码中,一个等价类集合会出现的内容,在实现中的对应如下:
- 普通变量:`members_`
- 值表达式:`value_expr_`
- phi函数:`value_phi_`
- 值编号v~i~:`index`
因此,伪代码中对集合的求交,对应到实现上,就是对以上四个域求交。
所以值编号的作用是指示同一个等价类,而不表示相等。
###
对于函数`valuePhiFunc(ve, P)`
###
# 我的设计
- 输入就一个分区,实际上要用到两个前驱的分区,怎么处理?
传入基本块做参数。
> 助教有提示过:
> 
>
> 我尝试了第2种,因为这个最简单,但是发现不可行:对loop3,第一次迭代进入`
label0
`,编号从3开始记,但是第二次迭代就要从5开始,因为`
label_Entry
`和`
label28
`相交,会拆分出`
%op1
`和`
%op7
`,而我还是index递增的逻辑,所以两个分别占用3、4,导致`
label0
`的编号从5开始,这样一来,在第一次迭代中编号为3的`
%op8
`无论如何也分不到编号3了。
- 二元运算的顺序颠倒怎么办?
> 如phi(x1,y1)+phi(x2,y2)和phi(x1,y1)+phi(y2,x2)
>
> 对于第二种,单纯寻找x1+y2和y1+x2肯定不合逻辑
上边不成立是有一个本质的原因的。比如说`
op1
`,第一次迭代和`
%op2
`、`
%op3
`在一个等价类中,第二次被拆出来占用了一个新的编号(假设是3),而`
%op8
`想嗔怪`
%op1
`占用了编号3,本来就不占理,因为`
%op8
`在最开始就应该从一个更大的编号开始使用,为前边的指令预留出充足的编号空间(数据流收敛的过程是等价类拆分的过程)。
我灵机一动,想出了下面这个维护方案:
- 首先,我遍历所有的指令是存在一个顺序的,我记录下这个信息
- 其次,等价类至多,也就是一条指令一个等价类
- 设计方案:每个指令的潜在值编号,是其遍历顺序。
应该不会有这种情况:phi表达式,追根溯源是在join时产生的:
对一条指令执行转移函数时,如果有可以归属的等价类,就插入之,
```cpp
GVN::join(const partitions &P1, const partitions &P2);
```
这里的分区是严格按照`
Basicblock
`的方法`
get_pre_basic_blocks()
`的返回顺序传入的,所以不会有担心的情况发生。
否则新建等价类的编号设置为遍历顺序。
### 所见非所得
这个方案可以保证一条指令执行后,如果新建了等价类,会产生相同的编号,以和后边的数据流标示同一个等价类,这样的编号空间预留可以避免编号冲突的问题。
两方面
:
实现上要注意两点
:
-
论文中伪代码对于基本块的关注不是很多,但是我们的实现一定要围绕基本块进行,这个的解决对策说到了,其实和单条语句没有本质差异。
-
每次调用针对指令的转移函数之前,都要重新设置值编号
-
我们针对lightir优化,经常深入API中陷入到实现细节,还容易被C++的语言特性绊住,但是应该尝试从直观上理解:我们究竟要实现什么样的效果。这个就得去看ll文件找灵感。然后这还不够,回过头来看代码又呆住了,因为ll语句和那些类型对应不上,这个就是容易忽视的一点:看lightir类型的print函数,这个能帮我们直观地串联起实现细节和表象的ll文件
。
-
`
Intersect(Ci, Cj)
`中可能发现两个等价类不是相同的值编号,这时要新建phi等价类,并赋予一个精准的值编号:两个前驱中的定值,分别对应一个值编号,选择遍历顺序靠前的那个
。
## 实验设计
这个的实现就`
Intersect
`比较讲究技巧了,其他写起来都挺直观的。
### 常量传播的实现
### join_helper
### 所见非所得
- 论文中伪代码对于基本块的关注不是很多,但是我们的实现一定要围绕基本块进行,这个的解决对策说到了,其实和单条语句没有本质差异。
### _TOP
- 我们针对lightir优化,经常深入API中陷入到实现细节,还容易被C++的语言特性绊住,但是应该尝试从直观上理解:我们究竟要实现什么样的效果。这个就得去看ll文件找灵感。然后这还不够,回过头来看代码又呆住了,因为ll语句和那些类型对应不上,这个就是容易忽视的一点:看lightir类型的print函数,这个能帮我们直观地串联起实现细节和表象的ll文件。
### transferFunction(Basicblock)
- 到了实验的后半阶段,调试信息的解读就变得越来越重要了。感谢某位同学劝我不要在用gdb debug,而是改用vscode,用gdb确实太慢了。
这里调试的困难其实不在于调试的技巧,更多是信息解读,很多时候,用了半个小时甚至一上午才明白错误的原因在哪里,这也来源于对算法的理解不足。
## 实验设计
###
> 设计见上述难点的描述
实现思路,相应代码,优化前后的IR对比(举一个例子)并辅以简单说明
...
...
gvn.json
0 → 100644
View file @
92ddf834
[{
"function"
:
"main"
,
"pout"
:
{
"label_entry"
:
[[
"%op0"
,
],
[
"%op1"
,
],
[
"%op2"
,
"%op3"
,
],
],}},]
\ No newline at end of file
src/optimization/GVN.cpp
View file @
92ddf834
...
...
@@ -263,9 +263,7 @@ GVN::intersect(shared_ptr<CongruenceClass> ci, shared_ptr<CongruenceClass> cj) {
if
(
c
->
members_
.
size
())
// not empty
{
if
(
c
->
index_
==
0
)
{
// it must be a phi instruction
// and be separated to 2 copy statement
// we should use the copy-stmt int the previous block
// we should use the `index` int the previous block
auto
instr
=
static_cast
<
Instruction
*>
(
*
c
->
members_
.
begin
());
auto
instr_phi
=
dynamic_cast
<
PhiInst
*>
(
instr
);
int
exact_idx
;
...
...
@@ -570,7 +568,6 @@ GVN::transferFunction(Instruction *instr, Value *e, partitions pin) {
}
// TODO: get different ValueExpr by Instruction::OpID, modify pout
// ??
// get ve and vpf
shared_ptr
<
Expression
>
ve
;
if
(
e
)
{
...
...
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