Commit 92ddf834 authored by 李晓奇's avatar 李晓奇

can pass loop3

parent 0f5a162d
# 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. 前言
......
......@@ -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肯定不合逻辑
论文中有详细的版本:
![](figures/value_phi_func.png)
### 等价类设计
> ```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`检测了这一点。
> ![](figures/loop3.png)
> 关注`%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)`
#### 我的设计
- 输入就一个分区,实际上要用到两个前驱的分区,怎么处理?
传入基本块做参数。
> 助教有提示过:
> ![](figures/index_set.png)
>
> 我尝试了第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对比(举一个例子)并辅以简单说明
......
[{
"function": "main",
"pout": {"label_entry": [["%op0", ], ["%op1", ], ["%op2", "%op3", ], ],}},]
\ No newline at end of file
......@@ -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) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment