Commit efbf4233 authored by 李晓奇's avatar 李晓奇

follow update

parent f0f0bb81
...@@ -6,7 +6,7 @@ AlwaysBreakTemplateDeclarations: true ...@@ -6,7 +6,7 @@ AlwaysBreakTemplateDeclarations: true
BinPackArguments: false BinPackArguments: false
BinPackParameters: false BinPackParameters: false
BreakConstructorInitializers: BeforeComma BreakConstructorInitializers: BeforeComma
ColumnLimit: 120 ColumnLimit: 80
CommentPragmas: '^(!|NOLINT)' CommentPragmas: '^(!|NOLINT)'
ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerAllOnOneLineOrOnePerLine: true
IncludeBlocks: Regroup IncludeBlocks: Regroup
...@@ -23,4 +23,5 @@ PenaltyReturnTypeOnItsOwnLine: 200 ...@@ -23,4 +23,5 @@ PenaltyReturnTypeOnItsOwnLine: 200
SpacesBeforeTrailingComments: 1 SpacesBeforeTrailingComments: 1
TabWidth: 4 TabWidth: 4
UseTab: Never UseTab: Never
AlwaysBreakAfterReturnType: TopLevelDefinitions
... ...
# Lab4 实验文档 # Lab4 实验文档
- [Lab4 实验文档](#lab4-实验文档) - [Lab4 实验文档](#lab4-实验文档)
- [0. 前言](#0-前言) - [0. 前言](#0-前言)
- [1. GVN 基础知识](#1-gvn-基础知识) - [1. GVN 基础知识](#1-gvn-基础知识)
- [1.1 GVN 简介](#11-gvn-简介) - [1.1 GVN 简介](#11-gvn-简介)
- [1.2 GVN 相关概念](#12-gvn-相关概念) - [1.2 GVN 相关概念](#12-gvn-相关概念)
- [1. IR 假设](#1-ir-假设) - [1. IR 假设](#1-ir-假设)
- [2. Expression 概念](#2-expression-概念) - [2. Expression 概念](#2-expression-概念)
- [3. 等价概念](#3-等价概念) - [3. 等价概念](#3-等价概念)
- [4. Value Expression 概念](#4-value-expression-概念) - [4. Value Expression 概念](#4-value-expression-概念)
- [5. Value phi-function 概念](#5-value-phi-function-概念) - [5. Value phi-function 概念](#5-value-phi-function-概念)
- [6. Partition](#6-partition) - [6. Partition](#6-partition)
- [7. Join 操作](#7-join-操作) - [7. Join 操作](#7-join-操作)
- [8. 变量等价与表达式等价](#8-变量等价与表达式等价) - [8. 变量等价与表达式等价](#8-变量等价与表达式等价)
- [9. Transfer Function](#9-transfer-function) - [9. Transfer Function](#9-transfer-function)
- [2. GVN 算法(论文中提供的伪代码)](#2-gvn-算法论文中提供的伪代码) - [2. GVN 算法(论文中提供的伪代码)](#2-gvn-算法论文中提供的伪代码)
- [3. 实验内容](#3-实验内容) - [3. 实验内容](#3-实验内容)
- [3.1 GVN pass 实现内容要求](#31-gvn-pass-实现内容要求) - [3.1 GVN pass 实现内容要求](#31-gvn-pass-实现内容要求)
- [3.2 GVN 辅助类](#32-gvn-辅助类) - [3.2 GVN 辅助类](#32-gvn-辅助类)
- [3.3 注册及运行 GVN Pass](#33-注册及运行-gvn-pass) - [3.3 注册及运行 GVN Pass](#33-注册及运行-gvn-pass)
- [注册 Pass](#注册-pass) - [注册 Pass](#注册-pass)
- [运行 Pass](#运行-pass) - [运行 Pass](#运行-pass)
- [3.3 自动测试](#33-自动测试) - [3.3 自动测试](#33-自动测试)
- [3.4 Tips](#34-tips) - [3.4 Tips](#34-tips)
- [4. 提交要求](#4-提交要求) - [4. 提交要求](#4-提交要求)
- [目录结构](#目录结构) - [目录结构](#目录结构)
- [提交要求和评分标准](#提交要求和评分标准) - [提交要求和评分标准](#提交要求和评分标准)
## 0. 前言
## 0. 前言
在 Lab4.1 中,我们介绍了 SSA IR,并阐明了其优势。本次实验中我们需要在 SSA IR 基础上,实现一个基于数据流分析的冗余消除的优化 Pass : Global Value Numbering(全局值编号)。 在 Lab4.1 中,我们介绍了 SSA IR,并阐明了其优势。本次实验中我们需要在 SSA IR 基础上,实现一个基于数据流分析的冗余消除的优化 Pass : Global Value Numbering(全局值编号)。
## 1. GVN 基础知识 ## 1. GVN 基础知识
### 1.1 GVN 简介 ### 1.1 GVN 简介
...@@ -74,7 +77,7 @@ bb2: ...@@ -74,7 +77,7 @@ bb2:
br bb3 br bb3
bb3: bb3:
... ...
``` ```
#### 3. 等价概念 #### 3. 等价概念
...@@ -110,6 +113,7 @@ bb3: ...@@ -110,6 +113,7 @@ bb3:
一个 Partition (分区)由一系列等价类组成,一个等价类是由一个值编号,和一系列成员组成。每个成员可以是:变量,常量,值表达式。同时,一个等价类可能会关联一个 value-phi-function。 一个 Partition (分区)由一系列等价类组成,一个等价类是由一个值编号,和一系列成员组成。每个成员可以是:变量,常量,值表达式。同时,一个等价类可能会关联一个 value-phi-function。
#### 7. Join 操作 #### 7. Join 操作
Join 操作检测到达此处的所有路径共有的等价项。在 SSA 格式的 IR 中,变量只会被赋值一次,当程序点 p 支配 join block 时,在 p 点成立的等价关系,在 join block 处仍然成立。通过对 join block 的前驱 Partition 取交集,可以保留所有支配 join block 的程序点的等价关系。对于在每个路径的等价的探测,我们将在[2.GVN算法](#2-gvn-算法论文中提供的伪代码)中通过伪代码进行阐述。对于表达式的等价关系与变量等价关系的检测与判定,我们会分别阐述。 Join 操作检测到达此处的所有路径共有的等价项。在 SSA 格式的 IR 中,变量只会被赋值一次,当程序点 p 支配 join block 时,在 p 点成立的等价关系,在 join block 处仍然成立。通过对 join block 的前驱 Partition 取交集,可以保留所有支配 join block 的程序点的等价关系。对于在每个路径的等价的探测,我们将在[2.GVN算法](#2-gvn-算法论文中提供的伪代码)中通过伪代码进行阐述。对于表达式的等价关系与变量等价关系的检测与判定,我们会分别阐述。
#### 8. 变量等价与表达式等价 #### 8. 变量等价与表达式等价
...@@ -263,6 +267,7 @@ valuePhiFunc(ve,P) ...@@ -263,6 +267,7 @@ valuePhiFunc(ve,P)
## 3. 实验内容 ## 3. 实验内容
在本次实验中,请仔细阅读[3.1 GVN pass 实现内容要求](#31-gvn-pass-实现内容要求),根据要求补全`src/optimization/GVN.cpp``include/optimization/GVN.h`中关于 GVN pass 数据流分析部分,同时需要在 `Reports/4-ir-opt/` 目录下撰写实验报告。**为了在评测中统一分析结果,请大家采用 lab3 的 TA-impl 分支提供的[答案](http://202.38.79.174/compiler_staff/2022fall-compiler_cminus/-/blob/TA-impl/src/cminusfc/cminusf_builder.cpp)来完成后续实验。** 在本次实验中,请仔细阅读[3.1 GVN pass 实现内容要求](#31-gvn-pass-实现内容要求),根据要求补全`src/optimization/GVN.cpp``include/optimization/GVN.h`中关于 GVN pass 数据流分析部分,同时需要在 `Reports/4-ir-opt/` 目录下撰写实验报告。**为了在评测中统一分析结果,请大家采用 lab3 的 TA-impl 分支提供的[答案](http://202.38.79.174/compiler_staff/2022fall-compiler_cminus/-/blob/TA-impl/src/cminusfc/cminusf_builder.cpp)来完成后续实验。**
### 3.1 GVN pass 实现内容要求 ### 3.1 GVN pass 实现内容要求
GVN 通过数据流分析来检测冗余的变量和计算,通过替换和死代码删除结合,实现优化效果。前述的例子中主要以二元运算语句来解释原理,且助教为大家提供了代码替换和删除的逻辑,除此之外,需要完成的方向有: GVN 通过数据流分析来检测冗余的变量和计算,通过替换和死代码删除结合,实现优化效果。前述的例子中主要以二元运算语句来解释原理,且助教为大家提供了代码替换和删除的逻辑,除此之外,需要完成的方向有:
...@@ -270,11 +275,11 @@ GVN 通过数据流分析来检测冗余的变量和计算,通过替换和死 ...@@ -270,11 +275,11 @@ GVN 通过数据流分析来检测冗余的变量和计算,通过替换和死
1. 对冗余指令的检测与消除包括(二元运算指令,cmp,gep,类型转换指令) 1. 对冗余指令的检测与消除包括(二元运算指令,cmp,gep,类型转换指令)
2. 对纯函数的冗余调用消除(助教提供了纯函数分析,见[FuncInfo.h](../../include/optimization/FuncInfo.h) 2. 对纯函数的冗余调用消除(助教提供了纯函数分析,见[FuncInfo.h](../../include/optimization/FuncInfo.h)
该 Pass 的接口`is_pure_function`接受一个lightIR Function,判断该函数是否为纯函数;对于纯函数,如果其参数等价,对这个纯函数的不同调用也等价。 该 Pass 的接口`is_pure_function`接受一个lightIR Function,判断该函数是否为纯函数;对于纯函数,如果其参数等价,对这个纯函数的不同调用也等价。
3. 常量传播 3. 常量传播
在数据流分析的过程中,可以使用常量折叠来实现常量传播,从而将可在编译时计算的结果计算好,减少运行时开销。(助教提供了常量折叠类,在辅助类的介绍中) 在数据流分析的过程中,可以使用常量折叠来实现常量传播,从而将可在编译时计算的结果计算好,减少运行时开销。(助教提供了常量折叠类,在辅助类的介绍中)
我们会在测试样例中对这三点进行考察。 我们会在测试样例中对这三点进行考察。
...@@ -403,27 +408,27 @@ root@3fd22a9ed627:/labs/2022fall-compiler_cminus-taversion/tests/4-ir-opt# ...@@ -403,27 +408,27 @@ root@3fd22a9ed627:/labs/2022fall-compiler_cminus-taversion/tests/4-ir-opt#
### 3.4 Tips ### 3.4 Tips
1. 为了大家能够循序渐进地实现 GVN pass,助教给出一个实现上的规划: 1. 为了大家能够循序渐进地实现 GVN pass,助教给出一个实现上的规划:
1. 使用 GVN pass 分析单一基本块的程序 case 1. 使用 GVN pass 分析单一基本块的程序 case
补充 `detectEquivalences` 函数(无需处理 Join ),在转移方程`transferFunction`中,为每个变量创建等价类。 补充 `detectEquivalences` 函数(无需处理 Join ),在转移方程`transferFunction`中,为每个变量创建等价类。
2. 使用 GVN pass 分析带有冗余计算的单一基本块的程序 case,并在分析过程中进行常量传播 2. 使用 GVN pass 分析带有冗余计算的单一基本块的程序 case,并在分析过程中进行常量传播
依照伪代码补全转移方程`transferFunction`,得到正确的等价类,将输出结构 dump 至 json 文件,与手动推算结果进行比对。 依照伪代码补全转移方程`transferFunction`,得到正确的等价类,将输出结构 dump 至 json 文件,与手动推算结果进行比对。
3. 使用 GVN pass 分析带有选择语句的程序 case,处理phi语句之间的冗余 3. 使用 GVN pass 分析带有选择语句的程序 case,处理phi语句之间的冗余
完善 `detectEquivalences` ,实现合并两个前驱分区的合并算法 `Join`,和 `valuePhiFunc`,注意本次实验只考察`phi(a+b, c+d)`与`phi(a,c)+phi(b,d)`之间的冗余。 完善 `detectEquivalences` ,实现合并两个前驱分区的合并算法 `Join`,和 `valuePhiFunc`,注意本次实验只考察`phi(a+b, c+d)``phi(a,c)+phi(b,d)`之间的冗余。
4. 正确分析带有循环的程序 4. 正确分析带有循环的程序
完成了前三点后,你的 GVN 应该比较完整了,可以根据带循环的简单程序来调试。 完成了前三点后,你的 GVN 应该比较完整了,可以根据带循环的简单程序来调试。
2. **Lab3** 的测试样例仍然可以用来测试优化正确性 2. **Lab3** 的测试样例仍然可以用来测试优化正确性
3. 使用 logging 工具来打印调试信息,以及 GDB 等软件进行单步调试来检查错误的原因 3. 使用 logging 工具来打印调试信息,以及 GDB 等软件进行单步调试来检查错误的原因
[logging](../common/logging.md) 是帮助大家打印调试信息的工具,如有需求可以阅读文档后进行使用 [logging](../common/logging.md) 是帮助大家打印调试信息的工具,如有需求可以阅读文档后进行使用
4. 你有可能会遇到智能指针相关的问题,详见[C++文档](https://zh.cppreference.com/w/cpp/memory/shared_ptr),以及[段错误调试指北](../common/simple_cpp.md#debugging-seg-fault) 4. 你有可能会遇到智能指针相关的问题,详见[C++文档](https://zh.cppreference.com/w/cpp/memory/shared_ptr),以及[段错误调试指北](../common/simple_cpp.md#debugging-seg-fault)
...@@ -449,97 +454,101 @@ root@3fd22a9ed627:/labs/2022fall-compiler_cminus-taversion/tests/4-ir-opt# ...@@ -449,97 +454,101 @@ root@3fd22a9ed627:/labs/2022fall-compiler_cminus-taversion/tests/4-ir-opt#
│   ├── optimization │   ├── optimization
│   │ ├── Mem2Reg.hpp Mem2Reg │   │ ├── Mem2Reg.hpp Mem2Reg
│ │ ├── Dominators.hpp 支配树 │ │ ├── Dominators.hpp 支配树
│ │ └── GVN.h GVN <--- 修改并提交 │ │ └── GVN.h GVN <--- 修改并提交
│   ├── cminusf_builder.hpp │   ├── cminusf_builder.hpp
| └── ast.hpp | └── ast.hpp
├── Reports ├── Reports
│   ├── ... │   ├── ...
│   └── 4.2-gvn │   └── 4.2-gvn
│   └── report.md lab4 本次实验报告模板 <--- 修改并提交 │   └── report.md lab4 本次实验报告模板 <--- 修改并提交
│   │  
├── src ├── src
│   ├── ... │   ├── ...
│   └── optimization │   └── optimization
│   ├── Mem2Reg.cpp Mem2Reg │   ├── Mem2Reg.cpp Mem2Reg
│ ├── Dominators.cpp 支配树 │ ├── Dominators.cpp 支配树
│ └── GVN.cpp GVN <--- 修改并提交 │ └── GVN.cpp GVN <--- 修改并提交
└── tests └── tests
├── ... ├── ...
└── 4-ir-opt └── 4-ir-opt
   ├── testcases 助教提供的测试样例    ├── testcases 助教提供的测试样例
   └── lab4_test.py 助教提供的测试脚本    └── lab4_test.py 助教提供的测试脚本
``` ```
### 提交要求和评分标准 ### 提交要求和评分标准
* 提交要求 * 提交要求
本实验是个人实验,需要将代码上传至gitlab实验仓库,并在希冀评测平台上提交仓库链接。 本实验是个人实验,需要将代码上传至gitlab实验仓库,并在希冀评测平台上提交仓库链接。
* 需要填补 * 需要填补
`include/optimization/GVN.h`, `src/optimization/GVN.cpp`,在 `Reports/4.2-gvn/report.md` 中撰写实验报告。 `include/optimization/GVN.h`, `src/optimization/GVN.cpp`,在 `Reports/4.2-gvn/report.md` 中撰写实验报告。
- 实验报告内容包括 - 实验报告内容包括
实验要求、实验难点、实验设计、实验结果验证、思考题、实验反馈等,具体见 `Reports/4.2-gvn/report.md` 实验要求、实验难点、实验设计、实验结果验证、思考题、实验反馈等,具体见 `Reports/4.2-gvn/report.md`
* 本次实验不提供希冀平台在线测试通道,将在 soft ddl 时收取同学仓库 `master` 分支下`include/optimization/GVN.h``src/optimization/GVN.cpp` 文件,请同学们在ddl前将自己的最新分支push到 git 仓库上 * 本次实验不提供希冀平台在线测试通道,将在 soft ddl 时收取同学仓库 `master` 分支下`include/optimization/GVN.h``src/optimization/GVN.cpp` 文件,请同学们在ddl前将自己的最新分支push到 git 仓库上
* 本次实验报告以 pdf 格式提交到希冀平台对应提交通道 * 本次实验报告以 pdf 格式提交到希冀平台对应提交通道
* 评分标准: 实验完成分(总分 60 分)组成如下: * 评分标准: 实验完成分(总分 60 分)组成如下:
* 实验报告 (5 分)
需要回答 Reports 目录下实验报告模板的思考题。
* easy case(40分)
助教提供了 4 个公开测试用例,并保留 4 个隐藏用例,根据每个用例 dump 出来的分析结果正误给分。json 文件判断的逻辑如下,仅对每个bb的 pout 进行判断。
例如:
```json * 实验报告 (5 分)
"pout": {
"label_entry": [["%op0", ], ["%op1", ], ["%op2", ], ], 需要回答 Reports 目录下实验报告模板的思考题。
"label3": [["%op0", ], ["%op1", ], ["%op2", ], ["%op4", "%op10", ], ["%op5", "%op9", ], ["%op6", "%op8", ], ],
"label7": [["%op0", ], ["%op1", ], ["%op2", ], ["%op10", ], ["%op9", ], ["%op11", "%op8", ], ], * easy case(40分)
"label12": [["%op0", ], ["%op1", ], ["%op2", ], ["%op13", "%op10", ], ["%op14", "%op9", ], ["%op15", "%op8", ], ],}
``` 助教提供了 4 个公开测试用例,并保留 4 个隐藏用例,根据每个用例 dump 出来的分析结果正误给分。json 文件判断的逻辑如下,仅对每个bb的 pout 进行判断。
对于分值为 x,n 个基本块的程序,每个 bb 分析结果为$`x/n`$分,某个bb的分析结果多或者少一个等价类,或有分析错误的等价类,该 bb 分析结果没有分值。
例如:
* performance case(15分) ```json
"pout": {
助教提供了 2 个公开case,并保留 2 个隐藏用例。以及助教实现优化后的 baseline.ll ,优化效果按照如下方式给分(执行结果不正确则此项分数为0) "label_entry": [["%op0", ], ["%op1", ], ["%op2", ], ],
"label3": [["%op0", ], ["%op1", ], ["%op2", ], ["%op4", "%op10", ], ["%op5", "%op9", ], ["%op6", "%op8", ], ],
"label7": [["%op0", ], ["%op1", ], ["%op2", ], ["%op10", ], ["%op9", ], ["%op11", "%op8", ], ],
"label12": [["%op0", ], ["%op1", ], ["%op2", ], ["%op13", "%op10", ], ["%op14", "%op9", ], ["%op15", "%op8", ], ],}
```
对于分值为 x,n 个基本块的程序,每个 bb 分析结果为$`x/n`$分,某个bb的分析结果多或者少一个等价类,或有分析错误的等价类,该 bb 分析结果没有分值。
``` * performance case(15分)
对于每一个testcase:
(before_optimization-after_optimization)/(before_optimization-baseline) > 0.8 得满分 助教提供了 2 个公开case,并保留 2 个隐藏用例。以及助教实现优化后的 baseline.ll ,优化效果按照如下方式给分(执行结果不正确则此项分数为0)
(before_optimization-after_optimization)/(before_optimization-baseline) > 0.5 得85%分数
(before_optimization-after_optimization)/(before_optimization-baseline) > 0.2 得60%分数 ```
``` 对于每一个testcase:
(before_optimization-after_optimization)/(before_optimization-baseline) > 0.8 得满分
(before_optimization-after_optimization)/(before_optimization-baseline) > 0.5 得85%分数
(before_optimization-after_optimization)/(before_optimization-baseline) > 0.2 得60%分数
```
* 禁止执行恶意代码,违者本次实验0分处理 * 禁止执行恶意代码,违者本次实验0分处理
* 迟交规定 * 迟交规定
* `Soft Deadline`: 2022/12/12 23:59:59 (北京标准时间,UTC+8) * `Soft Deadline`: 2022/12/12 23:59:59 (北京标准时间,UTC+8)
* `Hard Deadline`: 2022/12/19 23:59:59 (北京标准时间,UTC+8) * `Hard Deadline`: 2022/12/19 23:59:59 (北京标准时间,UTC+8)
* 迟交需要邮件通知 TA : * 迟交需要邮件通知 TA :
* 邮箱:
chen16614@mail.ustc.edu.cn 抄送 farmerzhang1@mail.ustc.edu.cn * 邮箱:
* 邮件主题: lab4.2迟交-学号 chen16614@mail.ustc.edu.cn 抄送 farmerzhang1@mail.ustc.edu.cn
* 内容: 包括迟交原因、最后版本commitID、迟交时间等 * 邮件主题: lab4.2迟交-学号
* 内容: 包括迟交原因、最后版本commitID、迟交时间等
* 迟交分数
* x为迟交天数(对于`Soft Deadline`而言),grade为满分 * 迟交分数
``` bash
final_grade = grade, x = 0 * x为迟交天数(对于`Soft Deadline`而言),grade为满分
final_grade = grade * (0.9)^x, 0 < x <= 7
final_grade = 0, x > 7 # 这一条严格执行,请对自己负责 ```bash
``` final_grade = grade, x = 0
final_grade = grade * (0.9)^x, 0 < x <= 7
final_grade = 0, x > 7 # 这一条严格执行,请对自己负责
```
* 关于抄袭和雷同 * 关于抄袭和雷同
经过助教和老师判定属于实验抄袭或雷同情况,所有参与方一律零分,不接受任何解释和反驳(严禁对开源代码或者其他同学代码的直接搬运)。 经过助教和老师判定属于实验抄袭或雷同情况,所有参与方一律零分,不接受任何解释和反驳(严禁对开源代码或者其他同学代码的直接搬运)。
......
...@@ -56,6 +56,7 @@ class Instruction : public User, public llvm::ilist_node<Instruction> ...@@ -56,6 +56,7 @@ class Instruction : public User, public llvm::ilist_node<Instruction>
Module *get_module(); Module *get_module();
OpID get_instr_type() const { return op_id_; } OpID get_instr_type() const { return op_id_; }
// clang-format off
static std::string get_instr_op_name(OpID id) { static std::string get_instr_op_name(OpID id) {
switch (id) { switch (id) {
case ret: return "ret"; break; case ret: return "ret"; break;
...@@ -79,12 +80,10 @@ class Instruction : public User, public llvm::ilist_node<Instruction> ...@@ -79,12 +80,10 @@ class Instruction : public User, public llvm::ilist_node<Instruction>
case zext: return "zext"; break; case zext: return "zext"; break;
case fptosi: return "fptosi"; break; case fptosi: return "fptosi"; break;
case sitofp: return "sitofp"; break; case sitofp: return "sitofp"; break;
default: return ""; break;
default:
return "";
break;
} }
} }
// clang-format on
std::string get_instr_op_name() { return get_instr_op_name(op_id_); } std::string get_instr_op_name() { return get_instr_op_name(op_id_); }
bool is_void() bool is_void()
......
...@@ -159,6 +159,7 @@ class GVN : public Pass { ...@@ -159,6 +159,7 @@ class GVN : public Pass {
partitions join(const partitions &P1, const partitions &P2); partitions join(const partitions &P1, const partitions &P2);
std::shared_ptr<CongruenceClass> intersect(std::shared_ptr<CongruenceClass>, std::shared_ptr<CongruenceClass>); std::shared_ptr<CongruenceClass> intersect(std::shared_ptr<CongruenceClass>, std::shared_ptr<CongruenceClass>);
partitions transferFunction(Instruction *x, Value *e, partitions pin); partitions transferFunction(Instruction *x, Value *e, partitions pin);
partitions transferFunction(BasicBlock *bb);
std::shared_ptr<GVNExpression::PhiExpression> valuePhiFunc(std::shared_ptr<GVNExpression::Expression>, std::shared_ptr<GVNExpression::PhiExpression> valuePhiFunc(std::shared_ptr<GVNExpression::Expression>,
const partitions &); const partitions &);
std::shared_ptr<GVNExpression::Expression> valueExpr(Instruction *instr); std::shared_ptr<GVNExpression::Expression> valueExpr(Instruction *instr);
...@@ -176,6 +177,10 @@ class GVN : public Pass { ...@@ -176,6 +177,10 @@ class GVN : public Pass {
return std::make_shared<CongruenceClass>(index); return std::make_shared<CongruenceClass>(index);
} }
// self add
//
std::uint64_t new_number() { return next_value_number_++; }
private: private:
bool dump_json_; bool dump_json_;
std::uint64_t next_value_number_ = 1; std::uint64_t next_value_number_ = 1;
...@@ -184,6 +189,9 @@ class GVN : public Pass { ...@@ -184,6 +189,9 @@ class GVN : public Pass {
std::unique_ptr<FuncInfo> func_info_; std::unique_ptr<FuncInfo> func_info_;
std::unique_ptr<GVNExpression::ConstFolder> folder_; std::unique_ptr<GVNExpression::ConstFolder> folder_;
std::unique_ptr<DeadCode> dce_; std::unique_ptr<DeadCode> dce_;
// self add
std::map<Instruction*, bool> _TOP;
}; };
bool operator==(const GVN::partitions &p1, const GVN::partitions &p2); bool operator==(const GVN::partitions &p1, const GVN::partitions &p2);
...@@ -22,65 +22,112 @@ using namespace GVNExpression; ...@@ -22,65 +22,112 @@ using namespace GVNExpression;
using std::string_literals::operator""s; using std::string_literals::operator""s;
using std::shared_ptr; using std::shared_ptr;
static auto get_const_int_value = [](Value *v) { return dynamic_cast<ConstantInt *>(v)->get_value(); }; static auto get_const_int_value = [](Value *v) {
static auto get_const_fp_value = [](Value *v) { return dynamic_cast<ConstantFP *>(v)->get_value(); }; return dynamic_cast<ConstantInt *>(v)->get_value();
};
static auto get_const_fp_value = [](Value *v) {
return dynamic_cast<ConstantFP *>(v)->get_value();
};
// Constant Propagation helper, folders are done for you // Constant Propagation helper, folders are done for you
Constant *ConstFolder::compute(Instruction *instr, Constant *value1, Constant *value2) { Constant *
ConstFolder::compute(Instruction *instr, Constant *value1, Constant *value2) {
auto op = instr->get_instr_type(); auto op = instr->get_instr_type();
switch (op) { switch (op) {
case Instruction::add: case Instruction::add:
return ConstantInt::get(get_const_int_value(value1) + get_const_int_value(value2), module_); return ConstantInt::get(get_const_int_value(value1) +
get_const_int_value(value2),
module_);
case Instruction::sub: case Instruction::sub:
return ConstantInt::get(get_const_int_value(value1) - get_const_int_value(value2), module_); return ConstantInt::get(get_const_int_value(value1) -
get_const_int_value(value2),
module_);
case Instruction::mul: case Instruction::mul:
return ConstantInt::get(get_const_int_value(value1) * get_const_int_value(value2), module_); return ConstantInt::get(get_const_int_value(value1) *
get_const_int_value(value2),
module_);
case Instruction::sdiv: case Instruction::sdiv:
return ConstantInt::get(get_const_int_value(value1) / get_const_int_value(value2), module_); return ConstantInt::get(get_const_int_value(value1) /
get_const_int_value(value2),
module_);
case Instruction::fadd: case Instruction::fadd:
return ConstantFP::get(get_const_fp_value(value1) + get_const_fp_value(value2), module_); return ConstantFP::get(get_const_fp_value(value1) +
get_const_fp_value(value2),
module_);
case Instruction::fsub: case Instruction::fsub:
return ConstantFP::get(get_const_fp_value(value1) - get_const_fp_value(value2), module_); return ConstantFP::get(get_const_fp_value(value1) -
get_const_fp_value(value2),
module_);
case Instruction::fmul: case Instruction::fmul:
return ConstantFP::get(get_const_fp_value(value1) * get_const_fp_value(value2), module_); return ConstantFP::get(get_const_fp_value(value1) *
get_const_fp_value(value2),
module_);
case Instruction::fdiv: case Instruction::fdiv:
return ConstantFP::get(get_const_fp_value(value1) / get_const_fp_value(value2), module_); return ConstantFP::get(get_const_fp_value(value1) /
get_const_fp_value(value2),
module_);
case Instruction::cmp: case Instruction::cmp:
switch (dynamic_cast<CmpInst *>(instr)->get_cmp_op()) { switch (dynamic_cast<CmpInst *>(instr)->get_cmp_op()) {
case CmpInst::EQ: case CmpInst::EQ:
return ConstantInt::get(get_const_int_value(value1) == get_const_int_value(value2), module_); return ConstantInt::get(get_const_int_value(value1) ==
get_const_int_value(value2),
module_);
case CmpInst::NE: case CmpInst::NE:
return ConstantInt::get(get_const_int_value(value1) != get_const_int_value(value2), module_); return ConstantInt::get(get_const_int_value(value1) !=
get_const_int_value(value2),
module_);
case CmpInst::GT: case CmpInst::GT:
return ConstantInt::get(get_const_int_value(value1) > get_const_int_value(value2), module_); return ConstantInt::get(get_const_int_value(value1) >
get_const_int_value(value2),
module_);
case CmpInst::GE: case CmpInst::GE:
return ConstantInt::get(get_const_int_value(value1) >= get_const_int_value(value2), module_); return ConstantInt::get(get_const_int_value(value1) >=
get_const_int_value(value2),
module_);
case CmpInst::LT: case CmpInst::LT:
return ConstantInt::get(get_const_int_value(value1) < get_const_int_value(value2), module_); return ConstantInt::get(get_const_int_value(value1) <
get_const_int_value(value2),
module_);
case CmpInst::LE: case CmpInst::LE:
return ConstantInt::get(get_const_int_value(value1) <= get_const_int_value(value2), module_); return ConstantInt::get(get_const_int_value(value1) <=
get_const_int_value(value2),
module_);
} }
case Instruction::fcmp: case Instruction::fcmp:
switch (dynamic_cast<FCmpInst *>(instr)->get_cmp_op()) { switch (dynamic_cast<FCmpInst *>(instr)->get_cmp_op()) {
case FCmpInst::EQ: case FCmpInst::EQ:
return ConstantInt::get(get_const_fp_value(value1) == get_const_fp_value(value2), module_); return ConstantInt::get(get_const_fp_value(value1) ==
get_const_fp_value(value2),
module_);
case FCmpInst::NE: case FCmpInst::NE:
return ConstantInt::get(get_const_fp_value(value1) != get_const_fp_value(value2), module_); return ConstantInt::get(get_const_fp_value(value1) !=
get_const_fp_value(value2),
module_);
case FCmpInst::GT: case FCmpInst::GT:
return ConstantInt::get(get_const_fp_value(value1) > get_const_fp_value(value2), module_); return ConstantInt::get(get_const_fp_value(value1) >
get_const_fp_value(value2),
module_);
case FCmpInst::GE: case FCmpInst::GE:
return ConstantInt::get(get_const_fp_value(value1) >= get_const_fp_value(value2), module_); return ConstantInt::get(get_const_fp_value(value1) >=
get_const_fp_value(value2),
module_);
case FCmpInst::LT: case FCmpInst::LT:
return ConstantInt::get(get_const_fp_value(value1) < get_const_fp_value(value2), module_); return ConstantInt::get(get_const_fp_value(value1) <
get_const_fp_value(value2),
module_);
case FCmpInst::LE: case FCmpInst::LE:
return ConstantInt::get(get_const_fp_value(value1) <= get_const_fp_value(value2), module_); return ConstantInt::get(get_const_fp_value(value1) <=
get_const_fp_value(value2),
module_);
} }
default: default:
return nullptr; return nullptr;
} }
} }
Constant *ConstFolder::compute(Instruction *instr, Constant *value1) { Constant *
ConstFolder::compute(Instruction *instr, Constant *value1) {
auto op = instr->get_instr_type(); auto op = instr->get_instr_type();
switch (op) { switch (op) {
case Instruction::sitofp: case Instruction::sitofp:
...@@ -95,22 +142,27 @@ Constant *ConstFolder::compute(Instruction *instr, Constant *value1) { ...@@ -95,22 +142,27 @@ Constant *ConstFolder::compute(Instruction *instr, Constant *value1) {
} }
namespace utils { namespace utils {
static std::string print_congruence_class(const CongruenceClass &cc) { static std::string
print_congruence_class(const CongruenceClass &cc) {
std::stringstream ss; std::stringstream ss;
if (cc.index_ == 0) { if (cc.index_ == 0) {
ss << "top class\n"; ss << "top class\n";
return ss.str(); return ss.str();
} }
ss << "\nindex: " << cc.index_ << "\nleader: " << cc.leader_->print() ss << "\nindex: " << cc.index_ << "\nleader: " << cc.leader_->print()
<< "\nvalue phi: " << (cc.value_phi_ ? cc.value_phi_->print() : "nullptr"s) << "\nvalue phi: "
<< "\nvalue expr: " << (cc.value_expr_ ? cc.value_expr_->print() : "nullptr"s) << "\nmembers: {"; << (cc.value_phi_ ? cc.value_phi_->print() : "nullptr"s)
<< "\nvalue expr: "
<< (cc.value_expr_ ? cc.value_expr_->print() : "nullptr"s)
<< "\nmembers: {";
for (auto &member : cc.members_) for (auto &member : cc.members_)
ss << member->print() << "; "; ss << member->print() << "; ";
ss << "}\n"; ss << "}\n";
return ss.str(); return ss.str();
} }
static std::string dump_cc_json(const CongruenceClass &cc) { static std::string
dump_cc_json(const CongruenceClass &cc) {
std::string json; std::string json;
json += "["; json += "[";
for (auto member : cc.members_) { for (auto member : cc.members_) {
...@@ -123,7 +175,8 @@ static std::string dump_cc_json(const CongruenceClass &cc) { ...@@ -123,7 +175,8 @@ static std::string dump_cc_json(const CongruenceClass &cc) {
return json; return json;
} }
static std::string dump_partition_json(const GVN::partitions &p) { static std::string
dump_partition_json(const GVN::partitions &p) {
std::string json; std::string json;
json += "["; json += "[";
for (auto cc : p) for (auto cc : p)
...@@ -132,7 +185,8 @@ static std::string dump_partition_json(const GVN::partitions &p) { ...@@ -132,7 +185,8 @@ static std::string dump_partition_json(const GVN::partitions &p) {
return json; return json;
} }
static std::string dump_bb2partition(const std::map<BasicBlock *, GVN::partitions> &map) { static std::string
dump_bb2partition(const std::map<BasicBlock *, GVN::partitions> &map) {
std::string json; std::string json;
json += "{"; json += "{";
for (auto [bb, p] : map) for (auto [bb, p] : map)
...@@ -142,7 +196,8 @@ static std::string dump_bb2partition(const std::map<BasicBlock *, GVN::partition ...@@ -142,7 +196,8 @@ static std::string dump_bb2partition(const std::map<BasicBlock *, GVN::partition
} }
// logging utility for you // logging utility for you
static void print_partitions(const GVN::partitions &p) { static void
print_partitions(const GVN::partitions &p) {
if (p.empty()) { if (p.empty()) {
LOG_DEBUG << "empty partitions\n"; LOG_DEBUG << "empty partitions\n";
return; return;
...@@ -154,53 +209,166 @@ static void print_partitions(const GVN::partitions &p) { ...@@ -154,53 +209,166 @@ static void print_partitions(const GVN::partitions &p) {
} }
} // namespace utils } // namespace utils
GVN::partitions GVN::join(const partitions &P1, const partitions &P2) { GVN::partitions
GVN::join(const partitions &P1, const partitions &P2) {
// TODO: do intersection pair-wise // TODO: do intersection pair-wise
return {}; partitions P{};
for (auto c1 : P1)
for (auto c2 : P2) {
auto c = intersect(c1, c2);
if (c->members_.empty())
continue;
P.insert(c);
}
return P;
} }
std::shared_ptr<CongruenceClass> GVN::intersect(std::shared_ptr<CongruenceClass> Ci, std::shared_ptr<CongruenceClass>
std::shared_ptr<CongruenceClass> Cj) { GVN::intersect(std::shared_ptr<CongruenceClass> ci,
std::shared_ptr<CongruenceClass> cj) {
// TODO // TODO
return {}; // If no common members, return null
auto c = createCongruenceClass();
std::set<Value *> intersection;
std::set_intersection(ci->members_.begin(),
ci->members_.end(),
cj->members_.begin(),
cj->members_.end(),
std::inserter(intersection, intersection.begin()));
c->members_ = intersection;
if (ci->index_ == cj->index_)
c->index_ = ci->index_;
if (ci->leader_ == cj->leader_)
c->leader_ = cj->leader_;
/* if (*ci == *cj)
* return ci; */
return c;
} }
void GVN::detectEquivalences() { void
bool changed = false; GVN::detectEquivalences() {
bool changed;
// initialize pout with top // initialize pout with top
for (auto &bb : func_->get_basic_blocks()) {
// pin_[&bb].clear();
// pout_[&bb].clear();
for (auto &instr : bb.get_instructions())
_TOP[&instr] = true;
}
// modify entry block
auto Entry = func_->get_entry_block();
_TOP[&*Entry->get_instructions().begin()] = false;
pin_[Entry].clear();
pout_[Entry].clear(); // pout_[Entry] = transferFunction(Entry);
// iterate until converge // iterate until converge
do { do {
changed = false;
// see the pseudo code in documentation // see the pseudo code in documentation
for (auto &bb : func_->get_basic_blocks()) { // you might need to visit the blocks in depth-first order for (auto &_bb :
func_->get_basic_blocks()) { // you might need to visit the
// blocks in depth-first order
auto bb = &_bb;
// get PIN of bb by predecessor(s) // get PIN of bb by predecessor(s)
auto pre_bbs_ = bb->get_pre_basic_blocks();
if (bb != Entry) {
// only update PIN for blocks that are not Entry
// that is: the PIN for entry is always empty
switch (pre_bbs_.size()) {
case 2: {
auto pre_1 = *pre_bbs_.begin();
auto pre_2 = *(++pre_bbs_.begin());
pin_[bb] = join(pin_[pre_1], pin_[pre_2]);
break;
}
case 1: {
auto pre = *(pre_bbs_.begin());
pin_[bb] = clone(pin_[pre]);
break;
}
default:
LOG_ERROR << "block has count of predecessors: "
<< pre_bbs_.size();
abort();
}
}
auto part = pin_[bb];
// iterate through all instructions in the block // iterate through all instructions in the block
for (auto &instr : bb->get_instructions()) {
// ??
if (not instr.is_phi())
part = transferFunction(&instr, instr.get_operand(1), part);
}
// and the phi instruction in all the successors // and the phi instruction in all the successors
for (auto succ : bb->get_succ_basic_blocks()) {
for (auto &instr : succ->get_instructions())
if (instr.is_phi()) {
Instruction *pretend;
// ??
part = transferFunction(
pretend, instr.get_operand(1), part);
}
}
// check changes in pout // check changes in pout
changed |= not(part == pout_[bb]);
pout_[bb] = part;
} }
} while (changed); } while (changed);
} }
shared_ptr<Expression> GVN::valueExpr(Instruction *instr) { shared_ptr<Expression>
GVN::valueExpr(Instruction *instr) {
// TODO // TODO
return {}; return {};
} }
// instruction of the form `x = e`, mostly x is just e (SSA), but for copy stmt x is a phi instruction in the // instruction of the form `x = e`, mostly x is just e (SSA),
// successor. Phi values (not copy stmt) should be handled in detectEquiv // but for copy stmt x is a phi instruction in the successor.
// Phi values (not copy stmt) should be handled in detectEquiv
/// \param bb basic block in which the transfer function is called /// \param bb basic block in which the transfer function is called
GVN::partitions GVN::transferFunction(Instruction *x, Value *e, partitions pin) { GVN::partitions
GVN::transferFunction(Instruction *x, Value *e, partitions pin) {
partitions pout = clone(pin); partitions pout = clone(pin);
// TODO: get different ValueExpr by Instruction::OpID, modify pout // TODO: get different ValueExpr by Instruction::OpID, modify pout
std::set<Value *>::iterator iter;
for (auto c : pout) {
if ((iter = std::find(c->members_.begin(), c->members_.end(), x)) !=
c->members_.end()) {
// static_cast<Value *>(x))) != c->members_.end()) {
c->members_.erase(iter);
}
}
auto ve = valueExpr(x);
auto vpf = valuePhiFunc(ve, pin);
/* pout.insert({});
* auto c = CongruenceClass(new_number());
* c.leader_ = e; */
return pout;
}
GVN::partitions
GVN::transferFunction(BasicBlock *bb) {
partitions pout = clone(pin_[bb]);
// ??
return pout; return pout;
} }
shared_ptr<PhiExpression> GVN::valuePhiFunc(shared_ptr<Expression> ve, const partitions &P) { shared_ptr<PhiExpression>
GVN::valuePhiFunc(shared_ptr<Expression> ve, const partitions &P) {
// TODO // TODO
return {}; return {};
} }
shared_ptr<Expression> GVN::getVN(const partitions &pout, shared_ptr<Expression> ve) { shared_ptr<Expression>
GVN::getVN(const partitions &pout, shared_ptr<Expression> ve) {
// TODO: return what? // TODO: return what?
for (auto it = pout.begin(); it != pout.end(); it++) for (auto it = pout.begin(); it != pout.end(); it++)
if ((*it)->value_expr_ and *(*it)->value_expr_ == *ve) if ((*it)->value_expr_ and *(*it)->value_expr_ == *ve)
...@@ -208,35 +376,45 @@ shared_ptr<Expression> GVN::getVN(const partitions &pout, shared_ptr<Expression> ...@@ -208,35 +376,45 @@ shared_ptr<Expression> GVN::getVN(const partitions &pout, shared_ptr<Expression>
return nullptr; return nullptr;
} }
void GVN::initPerFunction() { void
GVN::initPerFunction() {
next_value_number_ = 1; next_value_number_ = 1;
pin_.clear(); pin_.clear();
pout_.clear(); pout_.clear();
} }
void GVN::replace_cc_members() { void
GVN::replace_cc_members() {
for (auto &[_bb, part] : pout_) { for (auto &[_bb, part] : pout_) {
auto bb = _bb; // workaround: structured bindings can't be captured in C++17 auto bb =
_bb; // workaround: structured bindings can't be captured in C++17
for (auto &cc : part) { for (auto &cc : part) {
if (cc->index_ == 0) if (cc->index_ == 0)
continue; continue;
// if you are planning to do constant propagation, leaders should be set to constant at some point // if you are planning to do constant propagation, leaders should be
// set to constant at some point
for (auto &member : cc->members_) { for (auto &member : cc->members_) {
bool member_is_phi = dynamic_cast<PhiInst *>(member); bool member_is_phi = dynamic_cast<PhiInst *>(member);
bool value_phi = cc->value_phi_ != nullptr; bool value_phi = cc->value_phi_ != nullptr;
if (member != cc->leader_ and (value_phi or !member_is_phi)) { if (member != cc->leader_ and (value_phi or !member_is_phi)) {
// only replace the members if users are in the same block as bb // only replace the members if users are in the same block
member->replace_use_with_when(cc->leader_, [bb](User *user) { // as bb
if (auto instr = dynamic_cast<Instruction *>(user)) { member->replace_use_with_when(
auto parent = instr->get_parent(); cc->leader_, [bb](User *user) {
auto &bb_pre = parent->get_pre_basic_blocks(); if (auto instr =
if (instr->is_phi()) // as copy stmt, the phi belongs to this block dynamic_cast<Instruction *>(user)) {
return std::find(bb_pre.begin(), bb_pre.end(), bb) != bb_pre.end(); auto parent = instr->get_parent();
else auto &bb_pre = parent->get_pre_basic_blocks();
return parent == bb; if (instr->is_phi()) // as copy stmt, the phi
} // belongs to this block
return false; return std::find(bb_pre.begin(),
}); bb_pre.end(),
bb) != bb_pre.end();
else
return parent == bb;
}
return false;
});
} }
} }
} }
...@@ -245,7 +423,8 @@ void GVN::replace_cc_members() { ...@@ -245,7 +423,8 @@ void GVN::replace_cc_members() {
} }
// top-level function, done for you // top-level function, done for you
void GVN::run() { void
GVN::run() {
std::ofstream gvn_json; std::ofstream gvn_json;
if (dump_json_) { if (dump_json_) {
gvn_json.open("gvn.json", std::ios::out); gvn_json.open("gvn.json", std::ios::out);
...@@ -267,13 +446,15 @@ void GVN::run() { ...@@ -267,13 +446,15 @@ void GVN::run() {
detectEquivalences(); detectEquivalences();
LOG_INFO << "===============pin=========================\n"; LOG_INFO << "===============pin=========================\n";
for (auto &[bb, part] : pin_) { for (auto &[bb, part] : pin_) {
LOG_INFO << "\n===============bb: " << bb->get_name() << "=========================\npartitionIn: "; LOG_INFO << "\n===============bb: " << bb->get_name()
<< "=========================\npartitionIn: ";
for (auto &cc : part) for (auto &cc : part)
LOG_INFO << utils::print_congruence_class(*cc); LOG_INFO << utils::print_congruence_class(*cc);
} }
LOG_INFO << "\n===============pout=========================\n"; LOG_INFO << "\n===============pout=========================\n";
for (auto &[bb, part] : pout_) { for (auto &[bb, part] : pout_) {
LOG_INFO << "\n=====bb: " << bb->get_name() << "=====\npartitionOut: "; LOG_INFO << "\n=====bb: " << bb->get_name()
<< "=====\npartitionOut: ";
for (auto &cc : part) for (auto &cc : part)
LOG_INFO << utils::print_congruence_class(*cc); LOG_INFO << utils::print_congruence_class(*cc);
} }
...@@ -291,12 +472,15 @@ void GVN::run() { ...@@ -291,12 +472,15 @@ void GVN::run() {
} }
template <typename T> template <typename T>
static bool equiv_as(const Expression &lhs, const Expression &rhs) { static bool
// we use static_cast because we are very sure that both operands are actually T, not other types. equiv_as(const Expression &lhs, const Expression &rhs) {
// we use static_cast because we are very sure that both operands are
// actually T, not other types.
return static_cast<const T *>(&lhs)->equiv(static_cast<const T *>(&rhs)); return static_cast<const T *>(&lhs)->equiv(static_cast<const T *>(&rhs));
} }
bool GVNExpression::operator==(const Expression &lhs, const Expression &rhs) { bool
GVNExpression::operator==(const Expression &lhs, const Expression &rhs) {
if (lhs.get_expr_type() != rhs.get_expr_type()) if (lhs.get_expr_type() != rhs.get_expr_type())
return false; return false;
switch (lhs.get_expr_type()) { switch (lhs.get_expr_type()) {
...@@ -309,13 +493,17 @@ bool GVNExpression::operator==(const Expression &lhs, const Expression &rhs) { ...@@ -309,13 +493,17 @@ bool GVNExpression::operator==(const Expression &lhs, const Expression &rhs) {
} }
} }
bool GVNExpression::operator==(const shared_ptr<Expression> &lhs, const shared_ptr<Expression> &rhs) { bool
if (lhs == nullptr and rhs == nullptr) // is the nullptr check necessary here? GVNExpression::operator==(const shared_ptr<Expression> &lhs,
const shared_ptr<Expression> &rhs) {
if (lhs == nullptr and
rhs == nullptr) // is the nullptr check necessary here?
return true; return true;
return lhs and rhs and *lhs == *rhs; return lhs and rhs and *lhs == *rhs;
} }
GVN::partitions GVN::clone(const partitions &p) { GVN::partitions
GVN::clone(const partitions &p) {
partitions data; partitions data;
for (auto &cc : p) { for (auto &cc : p) {
data.insert(std::make_shared<CongruenceClass>(*cc)); data.insert(std::make_shared<CongruenceClass>(*cc));
...@@ -323,12 +511,18 @@ GVN::partitions GVN::clone(const partitions &p) { ...@@ -323,12 +511,18 @@ GVN::partitions GVN::clone(const partitions &p) {
return data; return data;
} }
bool operator==(const GVN::partitions &p1, const GVN::partitions &p2) { bool
operator==(const GVN::partitions &p1, const GVN::partitions &p2) {
// TODO: how to compare partitions? // TODO: how to compare partitions?
return false; // cannot direct compare???
if (p1.size() != p2.size())
return false;
return std::equal(p1.begin(), p1.end(), p2.begin(), p2.end());
} }
bool CongruenceClass::operator==(const CongruenceClass &other) const { // only compare index
bool
CongruenceClass::operator==(const CongruenceClass &other) const {
// TODO: which fields need to be compared? // TODO: which fields need to be compared?
return false; return index_ == other.index_;
} }
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