diff --git a/Documentations/4.2-gvn/README.md b/Documentations/4.2-gvn/README.md new file mode 100644 index 0000000000000000000000000000000000000000..cec6fec328f723597fbf97a196df33d754ea5007 --- /dev/null +++ b/Documentations/4.2-gvn/README.md @@ -0,0 +1,544 @@ +# Lab4 实验文档 +- [Lab4 实验文档](#lab4-实验文档) + - [0. 前言](#0-前言) + - [1. GVN 基础知识](#1-gvn-基础知识) + - [1.1 GVN 简介](#11-gvn-简介) + - [1.2 GVN 相关概念](#12-gvn-相关概念) + - [1. IR 假设](#1-ir-假设) + - [2. Expression 概念](#2-expression-概念) + - [3. 等价概念](#3-等价概念) + - [4. Value Expression 概念](#4-value-expression-概念) + - [5. Value phi-function 概念](#5-value-phi-function-概念) + - [6. Partition](#6-partition) + - [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) + - [注册 Pass](#注册-pass) + - [运行 Pass](#运行-pass) + - [3.3 自动测试](#33-自动测试) + - [3.4 Tips](#34-tips) + - [4. 提交要求](#4-提交要求) + - [目录结构](#目录结构) + - [提交要求和评分标准](#提交要求和评分标准) +## 0. 前言 + +在 Lab4.1 中,我们介绍了 SSA IR,并阐明了其优势。本次实验中我们需要在 SSA IR 基础上,实现一个基于数据流分析的冗余消除的优化 Pass : Global Value Numbering(全局值编号)。 +## 1. GVN 基础知识 + +### 1.1 GVN 简介 + +GVN(Global Value Numbering) 全局值编号,是一个基于 SSA 格式的优化,通过建立变量,表达式到值编号的映射关系,从而检测到冗余计算代码并删除。本次实验采用的算法参考论文为:**[Detection of Redundant Expressions: A Complete and Polynomial-Time Algorithm in SSA](./gvn.pdf)** 在该论文中,提出了一种适合 SSA IR ,多项式算法复杂度的数据流分析方式的算法,能够实现对冗余代码完备的检测。本次实验中,我们将在`Light IR` 上实现该数据流分析算法,并根据数据流分析结果删掉冗余代码,达到优化目的。 + +### 1.2 GVN 相关概念 + +#### 1. IR 假设 + +将IR抽象为具有空的`entry block`与`exit block`的控制流图(CFG)。块内包含形式为`x = e`的赋值语句,其中`e`为表达式,`x`为变量。每个bb块最多可以有两个前驱,有两个前驱的bb块被称为`join block`。 + +#### 2. Expression 概念 + +一个`Expression`(表达式)可以是一个常量,一个变量,或者是$`x⊕y`$的形式,其中 x 与 y 是常量或者变量, ⊕ 代表一个通用的二元运算符。一个`Expression`也可以是 $`\phi_k(x,y)`$ 的形式,其中 x, y 是变量,并且 k 表示 join block。这种形式的 `Expression`被称作 `phi` 函数。基于 Lab4.1 对`phi`指令概念的理解,我们知道 phi 函数是出现在 join block 的起始处,而在实际的数据流分析过程中,我们会将 `phi` 指令作为 join block 前驱的赋值指令进行处理。 + +**注**:如下例子所示:形如$`x_3=\phi(x_{1},x_{2})`$的 phi 指令。 + +```asm +bb1: + x1 = 1 + 1 + br bb3 + +bb2: + x2 = 2 + 2 + br bb3 + +bb3: + x3=phi(x1,x2) + ... +``` + +为了实现 SSA 上 GVN 分析的完备性,我们在数据流分析中,将 phi 指令在对应前驱中转化为 $`x_3 = x_1`$, $`x_3 = x_2`$ 两条赋值语句处理。如下例子所示: + +```asm +bb1: + x1 = 1 + 1 + x3 = x1 + br bb3 + +bb2: + x2 = 2 + 2 + x3 = x2 + br bb3 + +bb3: + ... +``` + +#### 3. 等价概念 + +两个表达式 `e1`,`e2` 如果被称作等价的,那么他们有着相同的运算符,并且操作数也是对应等价的。 + +#### 4. Value Expression 概念 + +一个 Value Expression(值表达式),$`vi⊕vj`$ 代表了值编号为vi,vj的两个等价类之间的运算。$`vi⊕vj=\{x⊕y;x\in C_i,C_i是一系列变量等价的等价类集合,值编号为v_i;y\in C_j,C_j是一系列变量等价的等价类的集合, 值编号是v_j\}`$ + +#### 5. Value phi-function 概念 + +与值表达式类似,value phi-function 视作为一系列等价的 phi-function 的抽象。在本次 GVN 实验中,需要扩展 phi-function 的理解,例如如下ir片段的例子: + +```asm +bb1: + y=x1+1 + br bb3 + +bb2: + z=x2+1 + br bb3 + +bb3: + x3=phi(x1,x2) + w3=x3+1 +``` + +对于 变量 w3 对应的 Expression x3+1,由于x3是phi function,因此可以通过[expression 概念](#2-expression-概念)中对phi function的处理探测出:w3,y在bb1中等价,w3,z在bb2中等价,因此可以将 w3 视作是变量 y 与变量 z 在 bb3 处的合并,可以用 $`\phi_{bb_3}(y,z)`$ 来表示w3对应的 value phi-function。 + +#### 6. Partition + +一个 Partition (分区)由一系列等价类组成,一个等价类是由一个值编号,和一系列成员组成。每个成员可以是:变量,常量,值表达式。同时,一个等价类可能会关联一个 value-phi-function。 + +#### 7. Join 操作 +Join 操作检测到达此处的所有路径共有的等价项。在 SSA 格式的 IR 中,变量只会被赋值一次,当程序点 p 支配 join block 时,在 p 点成立的等价关系,在 join block 处仍然成立。通过对 join block 的前驱 Partition 取交集,可以保留所有支配 join block 的程序点的等价关系。对于在每个路径的等价的探测,我们将在[2.GVN算法](#2-gvn-算法论文中提供的伪代码)中通过伪代码进行阐述。对于表达式的等价关系与变量等价关系的检测与判定,我们会分别阐述。 + +#### 8. 变量等价与表达式等价 + +例如如下代码段: + +```asm +bb1: + x1=1+1 + y1=2+2 + z1=x1+y1 + br bb3 + +bb2: + x2=2+2 + y2=3+3 + z2=x2+y2 + br bb3 + +bb3: + x3=phi(x1,x2) + y3=phi(y1,y2) + z3=phi(z1,z2) + z4=x3+y3 +``` + +在左分支bb1中,通过将 x3=phi(x1, x2)语句转换为前驱的复制语句 x3=x1 ,可以检测出,x3与x1在左分支路径上的等价性。同理也有x3与x2在右分支上的等价性。此为**变量等价** + +同时,通过对z4=x3+y3的分析,可以看出,x3+y3 这个表达式,在bb1中与x1+y1等价,在bb2中与x2+y2等价,此为**表达式等价**,通过这个表达式等价的关系,可以分析出,z4与z1在bb1中变量等价,z4与z2在bb2中变量等价,因此可以建立z4的phi函数为phi(z1,z2),进而可以导出z3与z4的**变量等价**,从而消除z3与z4的冗余。 + +#### 9. Transfer Function + +TransferFunction 作用于一条语句s:x=e 上,接受 partition 记为 $`PIN_s`$ 并探测 e 与到达此处程序点所有 path 上是否有等价的 expression。TransferFunction 通过更新原有的等价类,或者创建新的等价类来在 partition $`PIN_s`$ 基础上修改并输出 partition 记为 $`POUT_s`$。在后面给出的伪代码可以看出,TransferFunction先在$`PIN_s`$检测是否存在 expression e 的 value expression,然后会继续检查 e 是否可以表达为不同表达式的合并。考虑如下例子(沿用了在变量等价中的例子): + +```asm +bb1: + x1=1+1 + y1=2+2 + z1=x1+y1 + br bb3 + +bb2: + x2=2+2 + y2=3+3 + z2=x2+y2 + br bb3 + +bb3: + x3=phi(x1,x2) + y3=phi(y1,y2) + z3=phi(z1,z2) + z4=x3+y3 +``` + +在对`z4=x3+y3`这个表达式执行$`\text{TransferFunction}(x=e, PIN_s)`$: + +其中,x 为 z4, e 为 `x3+y3` +$`PIN_{bb3}=\{\{v_1,x_1,1+1:\text{non-phi}\},\{v_2,y_1,2+2:\text{non-phi}\},\{v_3,z_1,x_1+y_1:\text{non-phi}\},\{v_4,x_2,2+2:\text{non-phi}\},\{v_5,y_2,3+3:\text{non-phi}\},\{v_6,z_2,x_2+y_2:\text{non-phi}\},\{v_7,x_3:\phi(x_1,x_2)\},\{v_8,y_3:\phi(y_1,y_2)\},\{v_9,z_3:\phi(z_1,z_2)\}\}`$ + +在 $`PIN_{bb3}`$ 中找不到 `x3+y3` 对应的 expression 表达式。 + +但是,对 `x3+y3` 进一步分析可以得到:因为`x3`和`y3`都是`phi`,`x3+y3`可以表示为 `bb1` 中 `x1+y1` 与 `bb2` 中 `x2+y2` 的两个表达式的合并,因此可以记为 phi(`x1+y1`, `x2+y2`) + +接下来,可以检测到变量 z1 与 `x1+y1` expression 在`bb3`前驱`bb1`中的等价关系,变量 z2 与 `x2+y2` expression 在`bb3`前驱`bb2`中的等价关系。因此可以检测到 phi(`x1+y1`, `x2+y2`)与 phi(z1,z2)的等价关系。 + +进而得到 变量 z3 与 z4 是等价的,`x3 + y3` expression 的计算是冗余的。 + +注:这里的 $`PIN_{bb3}`$ 的例子仅仅是为了说明 TransferFunction 的职能和 partition 的结构式样,不代表最终迭代稳定后的结果。 + +## 2. GVN 算法(论文中提供的伪代码) + +本小节附上了原论文中附的伪代码,伪代码中给出了五个主函数的逻辑,这里自顶向下梳理一下: + +`detectEquivalences(G)` 包含了最主要的数据流迭代过程,传入参数 G 为抽象的数据流分析的 CFG 图结构,实现中可以以函数为分析的基本单位来执行此函数,迭代初始时,将各个 Partition 初始化为顶元`Top`, 定义为`Join(P, Top) = P = Join(Top, P)` + +注:此函数中的 statement 的概念对应前述的一条语句 + +```clike +detectEquivalences(G) + PIN1 = {} // “1” is the first statement in the program + POUT1 = transferFunction(PIN1) + for each statement s other than the first statement in the program + POUTs = Top + while changes to any POUT occur // i.e. changes in equivalences + for each statement s other than the first statement in the program + if s appears in block b that has two predecessors + then + PINs = Join(POUTs1, POUTs2) // s1 and s2 are last statements in respective predecessors + else + PINs = POUTs3 // s3 the statement just before s + POUTs = transferFunction(PINs) // apply transferFunction on each statement in the block +``` + +`Join` 操作仅仅在某个语句有两个前驱时被触发,注意:在join操作执行之前,join block 中的 phi 语句会作为 copy statement 加入 join block 对应的两个前驱末尾处。细节处理方式见[expression 概念](#2-expression-概念)的注栏。 + +```clike +Join(P1, P2) + P = {} + for each pair of classes Ci ∈ P1 and Cj ∈ P2 + Ck = Intersect(Ci, Cj) + P = P ∪ Ck // Ignore when Ck is empty + return P + +Intersect(Ci, Cj) + Ck = Ci ∩ Cj // set intersection + if Ck != {} and Ck does not have value number + then + Ck = Ck ∪ {vk} // vk is new value number + Ck = (Ck − {vpf}) ∪ {φb(vi, vj)} + // vpf is value φ-function in Ck, vi ∈ Ci, vj ∈ Cj, b is join block + return Ck +``` + +`TransferFunction` 接受一个赋值语句 x=e(x是变量,e为表达式),与一个 partition $`PIN_s`$ 。其中 getVN 从一个 partition 中根据 e 来找到对应的编号。 + +注: 在 lightir 的设计中,普通语句的 x 与 e 存在同一个类中,而 phi 语句中需要经转换为 copy 语句,x 与 e 与普通语句会有一些不同,需要仔细思考区分一下。 + +```clike +TransferFunction(x = e, PINs) + POUTs = PINs + if x is in a class Ci in POUTs + then Ci = Ci − {x} + ve = valueExpr(e) + vpf = valuePhiFunc(ve,PINs) // can be NULL + if ve or vpf is in a class Ci in POUTs // ignore vpf when NULL + then + Ci = Ci ∪ {x, ve} // set union + else + POUTs = POUTs ∪ {vn, x, ve : vpf} // vn is new value number + return POUTs + +valuePhiFunc(ve,P) + if ve is of the form φk(vi1, vj1) ⊕ φk(vi2, vj2) + then + // process left edge + vi = getVN(POUTkl, vi1 ⊕ vi2) + if vi is NULL + then vi = valuePhiFunc(vi1 ⊕ vi2, POUTkl) + // process right edge + vj = getVN(POUTkr, vj1 ⊕ vj2) + if vj is NULL + then vj = valuePhiFunc(vj1 ⊕ vj2, POUTkr) + + if vi is not NULL and vj is not NULL + then return φk(vi, vj) + else return NULL +``` + +## 3. 实验内容 + +在本次实验中,请仔细阅读[3.2 GVN pass 实现内容要求](#32-gvn-pass-实现内容要求),根据要求补全`src/optimization/GVN.cpp`,`include/optimization/GVN.cpp`中关于 GVN pass 数据流分析部分,同时需要在 `Reports/4-ir-opt/` 目录下撰写实验报告。**为了在评测中统一分析结果,请大家采用 lab3 的 TA-impl 分支提供的答案来继续后续实验。** +### 3.1 GVN pass 实现内容要求 + +GVN 通过数据流分析来检测冗余的变量和计算,通过替换和死代码删除结合,实现优化效果。前述的例子中主要以二元运算语句来解释原理,且助教为大家提供了代码替换和删除的逻辑,除此之外,需要完成的方向有: + +1. 对冗余指令的检测与消除包括(二元运算指令,cmp,gep,类型转换指令) + +2. 对纯函数的冗余调用消除(助教提供了纯函数分析,见[FuncInfo.h](../../include/optimization/FuncInfo.h)) + + 该 Pass 的接口`is_pure_function`接受一个lightIR Function,判断该函数是否为纯函数;对于纯函数,如果其参数等价,对这个纯函数的不同调用也等价。 + +3. 常量传播 + + 在数据流分析的过程中,可以使用常量折叠来实现常量传播,从而将可在编译时计算的结果计算好,减少运行时开销。(助教提供了常量折叠类,在辅助类的介绍中) + +我们会在测试样例中对这三点进行考察。 + +**注**:我们为大家提供了冗余删除的函数 `GVN::replace_cc_members` ,只需要正确填充在 `GVN` 类中的 `pout` 变量,我们的替换逻辑将会根据每个 bb 的 pout 自行使用`CongruenceClass` 的 `leader_` 成员来替换在此 bb 内与其等价其他指令。 + +### 3.2 GVN 辅助类 + +在上述对 GVN 概念的介绍中,为了能让大家专注于核心数据流分析逻辑的实现,我们为大家提供了一些相应实现的辅助类,并在注释里解释了其相应用途,请注意与前述的 GVN 的抽象概念结合,理解其设计,并补充必要的类成员的实现。 + +`GVN.h`: + +```c++ +class ConstFolder; // 常量折叠类,用于折叠操作数都是常量的指令 + +// 该 namespace 下,包含了用于判断 expression 等价的结构,我们提供了 binary expression,phi expression,constant expression 的结构,请根据测试用例添加你需要的其他 expression 的结构,具体细节请据 GVN.h 结合代码与注释理解 +namespace GVNExpression { +class Expression; // 所有 expression 类型的基类 +class ConstantExpression; // 常量 expression 类型 +class BinaryExpression; // 二元运算 expression 包括 + - * / +class PhiExpression; // phi expression 类型,表示不同路径的 expression 在此的合并 +} + +struct CongruenceClass; // 对应伪代码中等价类的概念,分析结果会根据此类 dump 至 json 文件中,代码替换与消除逻辑也根据此结构实现 + +class GVN; // GVN pass核心实现逻辑,除一些用的上的辅助函数外,重点补齐此处与伪代码对应的核心函数,其中run()函数是 pass 启动的入口,已经为大家补充好。 +``` + +`GVN.cpp`: + +```c++ +namespace utils // 一些用于输出的函数,可方便调试,以及将结果 dump 到 json 文件中的方法 +``` + +### 3.3 注册及运行 GVN Pass + +#### 注册 Pass + +本次实验使用了由 C++ 编写的 `LightIR` 来在 IR 层面完成优化化简,在`include/optimization/PassManager.hpp`中,定义了一个用于管理 Pass 的类`PassManager`。它的作用是注册与运行 Pass 。它提供了以下接口: + +```cpp +PassManager pm(module.get()) +pm.add_Pass(emit, dump_json) // 注册Pass,emit为true时打印优化后的IR +pm.run() // 按照注册的顺序运行 Pass 的 run() 函数 +``` + +#### 运行 Pass + +```sh +mkdir build && cd build +cmake .. +make -j +make install +``` + +为了便于大家进行实验,助教对之前的`cminusfc`增加了选项,用来选择是否开启某种优化,通过`-mem2reg`开关来控制优化 Pass 的使用,当需要对 `.cminus` 文件测试时,可以这样使用: + +```bash +./cminusfc [ -mem2reg ] [ -gvn [ -dump-json ] ] +``` + +其中,gvn pass 需要在 mem2reg pass 运行后运行。 + +### 3.3 自动测试 + +助教贴心地为大家准备了自动测试脚本,它在 `tests/4-ir-opt` 目录下,使用方法如下: + +```bash +python3 lab4_evals.py [ -gvn-analysis ] [ -gvn ] +``` + +该脚本可以在任意目录下运行 + +```bash +python3 tests/4-ir-opt/lab4_evals.py [ -gvn-analysis ] [ -gvn ] +``` + +其中 `-gvn-analysis` 对 GVN 分析结果的正确性进行判断,执行结果如下所示: + +```bash +========== GlobalValueNumberAnalysis ========== +Compiling -mem2reg -emit-llvm -gvn -dump-json + 0%| | 0/4 [00:00 0.8 得满分 + (before_optimization-after_optimization)/(before_optimization-baseline) > 0.5 得85%分数 + (before_optimization-after_optimization)/(before_optimization-baseline) > 0.2 得60%分数 + ``` + + * 禁止执行恶意代码,违者本次实验0分处理 + +* 迟交规定 + + * `Soft Deadline`: 2022/12/12 23:59:59 (北京标准时间,UTC+8) + + * `Hard Deadline`: 2022/12/19 23:59:59 (北京标准时间,UTC+8) + + * 迟交需要邮件通知 TA : + * 邮箱: + chen16614@mail.ustc.edu.cn 抄送 farmerzhang1@mail.ustc.edu.cn + * 邮件主题: lab4.2迟交-学号 + * 内容: 包括迟交原因、最后版本commitID、迟交时间等 + + * 迟交分数 + * x为迟交天数(对于`Soft Deadline`而言),grade为满分 + ``` bash + final_grade = grade, x = 0 + final_grade = grade * (0.9)^x, 0 < x <= 7 + final_grade = 0, x > 7 # 这一条严格执行,请对自己负责 + ``` + +* 关于抄袭和雷同 + 经过助教和老师判定属于实验抄袭或雷同情况,所有参与方一律零分,不接受任何解释和反驳(严禁对开源代码或者其他同学代码的直接搬运)。 + 如有任何问题,欢迎提issue进行批判指正。 diff --git a/Documentations/4.2-gvn/gvn.pdf b/Documentations/4.2-gvn/gvn.pdf new file mode 100644 index 0000000000000000000000000000000000000000..87b2c577f0674df1b6ebbcaf9669b949ddea7030 Binary files /dev/null and b/Documentations/4.2-gvn/gvn.pdf differ diff --git a/Documentations/common/simple_cpp.md b/Documentations/common/simple_cpp.md index 60a07e2dccfeead09e7e8aa2b522d6b78bd3caad..42c88f897d98af3a58d9a39b434cd631a3c60597 100644 --- a/Documentations/common/simple_cpp.md +++ b/Documentations/common/simple_cpp.md @@ -106,3 +106,13 @@ C中,只能使用标准库中的`malloc`与`free`来进行内存分配,并 1. `std::shared_ptr`: 引用计数智能指针,使用一个共享变量来记录指针管理的对象被引用了几次。当对象引用计数为0时,说明当前该对象不再有引用,并且进程也无法再通过其它方式来引用它,也就意味着可以回收内存,这相当于低级的垃圾回收策略。 2. `std::unique_ptr`: 表示所有权的智能指针,该指针要求它所管理的对象智能有一次引用,主要用于语义上不允许共享的对象(比如`llvm::Module`)。当引用计数为0时,它也会回收内存。 +## Debugging Seg Fault +Lab3/4 中,你可能会各种各样的段错误,不要害怕!clang 提供了一个工具来帮助我们解决内存泄漏。 +```bash +cd build +cmake .. -DCMAKE_BUILD_TYPE=Asan +make +``` +然后再次运行你的错误代码, Asan 会提供更详细的报错信息。 + +注:要更换到别的 build type (如Debug或Release)时需要显式指定,否则 cmake 会使用 cached 的版本。 diff --git a/Reports/4.2-gvn/report.md b/Reports/4.2-gvn/report.md new file mode 100644 index 0000000000000000000000000000000000000000..d290e359f9533ec66fe37e41d1cefaed08ed8b1a --- /dev/null +++ b/Reports/4.2-gvn/report.md @@ -0,0 +1,28 @@ +# Lab4.2 实验报告 + +姓名 学号 + +## 实验要求 + +请按照自己的理解,写明本次实验需要做什么 + +## 实验难点 + +实验中遇到哪些挑战 + +## 实验设计 +实现思路,相应代码,优化前后的IR对比(举一个例子)并辅以简单说明 + +### 思考题 +1. 请简要分析你的算法复杂度 +2. `std::shared_ptr`如果存在环形引用,则无法正确释放内存,你的 Expression 类是否存在 circular reference? +3. 尽管本次实验已经写了很多代码,但是在算法上和工程上仍然可以对 GVN 进行改进,请简述你的 GVN 实现可以改进的地方 + +## 实验总结 + +此次实验有什么收获 + +## 实验反馈(可选 不会评分) + +对本次实验的建议 + diff --git a/include/lightir/Instruction.h b/include/lightir/Instruction.h index ccf46c50efe5b9444f9f8bbd50cdc184805cf715..738aad63f12fc33f8aff7539c2f43249e6365233 100644 --- a/include/lightir/Instruction.h +++ b/include/lightir/Instruction.h @@ -55,8 +55,8 @@ class Instruction : public User, public llvm::ilist_node { Module *get_module(); OpID get_instr_type() const { return op_id_; } - std::string get_instr_op_name() { - switch (op_id_) { + static std::string get_instr_op_name(OpID id) { + switch (id) { case ret: return "ret"; break; case br: return "br"; break; case add: return "add"; break; @@ -82,6 +82,7 @@ class Instruction : public User, public llvm::ilist_node { default: return ""; break; } } + std::string get_instr_op_name() { return get_instr_op_name(op_id_); } bool is_void() { return ((op_id_ == ret) || (op_id_ == br) || (op_id_ == store) || diff --git a/include/lightir/Value.h b/include/lightir/Value.h index 4eeb018231e7c79cfdb36535dba5982b9aef6ef9..5fb6a8850bcef3d782d176c045aa5a916e2cf7d8 100644 --- a/include/lightir/Value.h +++ b/include/lightir/Value.h @@ -1,12 +1,14 @@ #ifndef SYSYC_VALUE_H #define SYSYC_VALUE_H +#include #include #include #include class Type; class Value; +class User; struct Use { Value *val_; @@ -35,6 +37,8 @@ class Value { std::string get_name() const; void replace_all_use_with(Value *new_val); + /// replace `value` with `new_val` when the user of value satisfies predicate `pred` + void replace_use_with_when(Value *new_val, std::function pred); void remove_use(Value *val); virtual std::string print() = 0; diff --git a/include/optimization/DeadCode.h b/include/optimization/DeadCode.h new file mode 100644 index 0000000000000000000000000000000000000000..ef5a80380c6b9640d2a9b08d40872a7882be347d --- /dev/null +++ b/include/optimization/DeadCode.h @@ -0,0 +1,106 @@ +#pragma once +#include "FuncInfo.h" +#include "Function.h" +#include "Instruction.h" +#include "PassManager.hpp" +#include "logging.hpp" + +/** + * 死代码消除:参见 https://www.clear.rice.edu/comp512/Lectures/10Dead-Clean-SCCP.pdf + **/ +class DeadCode : public Pass { + public: + DeadCode(Module *m) : Pass(m), func_info(std::make_shared(m)) {} + + // 处理流程:两趟处理,mark 标记有用变量,sweep 删除无用指令 + void run() { + bool changed{}; + func_info->run(); + do { + changed = false; + for (auto &F : m_->get_functions()) { + auto func = &F; + mark(func); + changed |= sweep(func); + } + } while (changed); + LOG_INFO << "dead code pass erased " << ins_count << " instructions"; + } + + private: + std::shared_ptr func_info; + void mark(Function *func) { + work_list.clear(); + marked.clear(); + + for (auto &bb : func->get_basic_blocks()) { + for (auto &ins : bb.get_instructions()) { + if (is_critical(&ins)) { + marked[&ins] = true; + work_list.push_back(&ins); + } + } + } + + while (work_list.empty() == false) { + auto now = work_list.front(); + work_list.pop_front(); + + mark(now); + } + } + void mark(Instruction *ins) { + for (auto op : ins->get_operands()) { + auto def = dynamic_cast(op); + if (def == nullptr) + continue; + if (marked[def]) + continue; + if (def->get_function() != ins->get_function()) + continue; + marked[def] = true; + work_list.push_back(def); + } + } + bool sweep(Function *func) { + std::unordered_set wait_del{}; + for (auto &bb : func->get_basic_blocks()) { + for (auto it = bb.get_instructions().begin(); it != bb.get_instructions().end();) { + if (marked[&*it]) { + ++it; + continue; + } else { + auto tmp = &*it; + wait_del.insert(tmp); + it++; + } + } + } + for (auto inst : wait_del) + inst->remove_use_of_ops(); + for (auto inst : wait_del) + inst->get_parent()->get_instructions().erase(inst); + ins_count += wait_del.size(); + return not wait_del.empty(); // changed + } + bool is_critical(Instruction *ins) { + // 对纯函数的无用调用也可以在删除之列 + if (ins->is_call()) { + auto call_inst = dynamic_cast(ins); + auto callee = dynamic_cast(call_inst->get_operand(0)); + if (func_info->is_pure_function(callee)) + return false; + return true; + } + if (ins->is_br() || ins->is_ret()) + return true; + if (ins->is_store()) + return true; + return false; + } + + // 用以衡量死代码消除的性能 + int ins_count{0}; + std::deque work_list{}; + std::unordered_map marked{}; +}; diff --git a/include/optimization/FuncInfo.h b/include/optimization/FuncInfo.h new file mode 100644 index 0000000000000000000000000000000000000000..1ffe1289da0b7d39a47e4db1397a4eab2d1ccdb6 --- /dev/null +++ b/include/optimization/FuncInfo.h @@ -0,0 +1,131 @@ +#pragma once +#include "Function.h" +#include "PassManager.hpp" +#include "logging.hpp" + +#include +#include +#include + +/** + * 计算哪些函数是纯函数 + * WARN: 假定所有函数都是纯函数,除非他写入了全局变量、修改了传入的数组、或者直接间接调用了非纯函数 + */ +class FuncInfo : public Pass { + public: + FuncInfo(Module *m) : Pass(m) {} + + void run() { + for (auto &f : m_->get_functions()) { + auto func = &f; + trivial_mark(func); + if (not is_pure[func]) + worklist.push_back(func); + } + while (worklist.empty() == false) { + auto now = worklist.front(); + worklist.pop_front(); + process(now); + } + log(); + } + + bool is_pure_function(Function *func) const { + // exit_if(is_pure.find(func) == is_pure.end(), ERROR_IN_PURE_FUNCTION_ANALYSIS); + return is_pure.at(func); + } + + void log() { + for (auto it : is_pure) { + LOG_INFO << it.first->get_name() << " is pure? " << it.second; + } + } + + private: + // 有 store 操作的函数非纯函数来处理 + void trivial_mark(Function *func) { + if (func->is_declaration() or func->get_name() == "main") { + is_pure[func] = false; + return; + } + // 只要传入数组,都作为非纯函数处理 + for (auto it = func->get_function_type()->param_begin(); it != func->get_function_type()->param_end(); ++it) { + auto arg_type = *it; + if (arg_type->is_integer_type() == false and arg_type->is_float_type() == false) { + is_pure[func] = false; + return; + } + } + for (auto &bb : func->get_basic_blocks()) + for (auto &inst : bb.get_instructions()) { + if (is_side_effect_inst(&inst)) { + is_pure[func] = false; + return; + } + } + is_pure[func] = true; + } + + void process(Function *func) { + for (auto &use : func->get_use_list()) { + LOG_INFO << use.val_->print() << " uses func: " << func->get_name(); + if (auto inst = dynamic_cast(use.val_)) { + auto func = (inst->get_parent()->get_parent()); + if (is_pure[func]) { + is_pure[func] = false; + worklist.push_back(func); + } + } else + LOG_WARNING << "Value besides instruction uses a function"; + } + } + + // 对局部变量进行 store 没有副作用 + bool is_side_effect_inst(Instruction *inst) { + if (inst->is_store()) { + if (is_local_store(dynamic_cast(inst))) + return false; + return true; + } + if (inst->is_load()) { + if (is_local_load(dynamic_cast(inst))) + return false; + return true; + } + // call 指令的副作用会在后续 bfs 中计算 + return false; + } + + bool is_local_load(LoadInst *inst) { + auto addr = dynamic_cast(get_first_addr(inst->get_operand(0))); + if (addr and addr->is_alloca()) + return true; + return false; + } + + bool is_local_store(StoreInst *inst) { + auto addr = dynamic_cast(get_first_addr(inst->get_lval())); + if (addr and addr->is_alloca()) + return true; + return false; + } + Value *get_first_addr(Value *val) { + if (auto inst = dynamic_cast(val)) { + if (inst->is_alloca()) + return inst; + if (inst->is_gep()) + return get_first_addr(inst->get_operand(0)); + if (inst->is_load()) + return val; + LOG_WARNING << "FuncInfo: try to determine addr in operands"; + for (auto op : inst->get_operands()) { + if (op->get_type()->is_pointer_type()) + return get_first_addr(op); + } + } + return val; + } + + std::deque worklist; + std::unordered_map is_pure; +}; diff --git a/include/optimization/GVN.h b/include/optimization/GVN.h new file mode 100644 index 0000000000000000000000000000000000000000..6b794acc8ddb7991b1fd42f0f9d1c1c0d6902f85 --- /dev/null +++ b/include/optimization/GVN.h @@ -0,0 +1,189 @@ +#pragma once +#include "BasicBlock.h" +#include "Constant.h" +#include "DeadCode.h" +#include "FuncInfo.h" +#include "Function.h" +#include "Instruction.h" +#include "Module.h" +#include "PassManager.hpp" +#include "Value.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace GVNExpression { + +// fold the constant value +class ConstFolder { + public: + ConstFolder(Module *m) : module_(m) {} + Constant *compute(Instruction *instr, Constant *value1, Constant *value2); + Constant *compute(Instruction *instr, Constant *value1); + + private: + Module *module_; +}; + +/** + * for constructor of class derived from `Expression`, we make it public + * because `std::make_shared` needs the constructor to be publicly available, + * but you should call the static factory method `create` instead the constructor itself to get the desired data + */ +class Expression { + public: + // TODO: you need to extend expression types according to testcases + enum gvn_expr_t { e_constant, e_bin, e_phi }; + Expression(gvn_expr_t t) : expr_type(t) {} + virtual ~Expression() = default; + virtual std::string print() = 0; + gvn_expr_t get_expr_type() const { return expr_type; } + + private: + gvn_expr_t expr_type; +}; + +bool operator==(const std::shared_ptr &lhs, const std::shared_ptr &rhs); +bool operator==(const GVNExpression::Expression &lhs, const GVNExpression::Expression &rhs); + +class ConstantExpression : public Expression { + public: + static std::shared_ptr create(Constant *c) { return std::make_shared(c); } + virtual std::string print() { return c_->print(); } + // we leverage the fact that constants in lightIR have unique addresses + bool equiv(const ConstantExpression *other) const { return c_ == other->c_; } + ConstantExpression(Constant *c) : Expression(e_constant), c_(c) {} + + private: + Constant *c_; +}; + +// arithmetic expression +class BinaryExpression : public Expression { + public: + static std::shared_ptr create(Instruction::OpID op, + std::shared_ptr lhs, + std::shared_ptr rhs) { + return std::make_shared(op, lhs, rhs); + } + virtual std::string print() { + return "(" + Instruction::get_instr_op_name(op_) + " " + lhs_->print() + " " + rhs_->print() + ")"; + } + + bool equiv(const BinaryExpression *other) const { + if (op_ == other->op_ and *lhs_ == *other->lhs_ and *rhs_ == *other->rhs_) + return true; + else + return false; + } + + BinaryExpression(Instruction::OpID op, std::shared_ptr lhs, std::shared_ptr rhs) + : Expression(e_bin), op_(op), lhs_(lhs), rhs_(rhs) {} + + private: + Instruction::OpID op_; + std::shared_ptr lhs_, rhs_; +}; + +class PhiExpression : public Expression { + public: + static std::shared_ptr create(std::shared_ptr lhs, std::shared_ptr rhs) { + return std::make_shared(lhs, rhs); + } + virtual std::string print() { return "(phi " + lhs_->print() + " " + rhs_->print() + ")"; } + bool equiv(const PhiExpression *other) const { + if (*lhs_ == *other->lhs_ and *rhs_ == *other->rhs_) + return true; + else + return false; + } + PhiExpression(std::shared_ptr lhs, std::shared_ptr rhs) + : Expression(e_phi), lhs_(lhs), rhs_(rhs) {} + + private: + std::shared_ptr lhs_, rhs_; +}; +} // namespace GVNExpression + +/** + * Congruence class in each partitions + * note: for constant propagation, you might need to add other fields + * and for load/store redundancy detection, you most certainly need to modify the class + */ +struct CongruenceClass { + size_t index_; + // representative of the congruence class, used to replace all the members (except itself) when analysis is done + Value *leader_; + // value expression in congruence class + std::shared_ptr value_expr_; + // value φ-function is an annotation of the congruence class + std::shared_ptr value_phi_; + // equivalent variables in one congruence class + std::set members_; + + CongruenceClass(size_t index) : index_(index), leader_{}, value_expr_{}, value_phi_{}, members_{} {} + + bool operator<(const CongruenceClass &other) const { return this->index_ < other.index_; } + bool operator==(const CongruenceClass &other) const; +}; + +namespace std { +template <> +// overload std::less for std::shared_ptr, i.e. how to sort the congruence classes +struct less> { + bool operator()(const std::shared_ptr &a, const std::shared_ptr &b) const { + // nullptrs should never appear in partitions, so we just dereference it + return *a < *b; + } +}; +} // namespace std + +class GVN : public Pass { + public: + using partitions = std::set>; + GVN(Module *m, bool dump_json) : Pass(m), dump_json_(dump_json) {} + // pass start + void run() override; + // init for pass metadata; + void initPerFunction(); + + // fill the following functions according to Pseudocode, **you might need to add more arguments** + void detectEquivalences(); + partitions join(const partitions &P1, const partitions &P2); + std::shared_ptr intersect(std::shared_ptr, std::shared_ptr); + partitions transferFunction(Instruction *x, Value *e, partitions pin); + std::shared_ptr valuePhiFunc(std::shared_ptr, + const partitions &); + std::shared_ptr valueExpr(Instruction *instr); + std::shared_ptr getVN(const partitions &pout, + std::shared_ptr ve); + + // replace cc members with leader + void replace_cc_members(); + + // note: be careful when to use copy constructor or clone + partitions clone(const partitions &p); + + // create congruence class helper + std::shared_ptr createCongruenceClass(size_t index = 0) { + return std::make_shared(index); + } + + private: + bool dump_json_; + std::uint64_t next_value_number_ = 1; + Function *func_; + std::map pin_, pout_; + std::unique_ptr func_info_; + std::unique_ptr folder_; + std::unique_ptr dce_; +}; + +bool operator==(const GVN::partitions &p1, const GVN::partitions &p2); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8875e3d24b68de59843a57bb0fbf38170d0e5d61..bae084948786aa6d4062be6371567dd7f4b0f4b7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,4 +3,4 @@ add_subdirectory(common) add_subdirectory(io) add_subdirectory(lightir) add_subdirectory(cminusfc) -add_subdirectory(optimization) \ No newline at end of file +add_subdirectory(optimization) diff --git a/src/cminusfc/cminusfc.cpp b/src/cminusfc/cminusfc.cpp index 880d879cc62469172dafe668b7f43ca161358528..7c705a2583635c0d33e114c55c51ed5994bf99e1 100644 --- a/src/cminusfc/cminusfc.cpp +++ b/src/cminusfc/cminusfc.cpp @@ -1,4 +1,5 @@ #include "Dominators.h" +#include "GVN.h" #include "Mem2Reg.hpp" #include "PassManager.hpp" #include "cminusf_builder.hpp" @@ -11,7 +12,8 @@ using namespace std::literals::string_literals; void print_help(std::string exe_name) { - std::cout << "Usage: " << exe_name << " [ -h | --help ] [ -o ] [ -emit-llvm ] [-mem2reg] " + std::cout << "Usage: " << exe_name + << " [ -h | --help ] [ -o ] [ -emit-llvm ] [-mem2reg] [-gvn] [-dump-json] " << std::endl; } @@ -20,6 +22,8 @@ int main(int argc, char **argv) { std::string input_path; bool mem2reg = false; + bool gvn = false; + bool dump_json = false; bool emit = false; for (int i = 1; i < argc; ++i) { @@ -38,6 +42,10 @@ int main(int argc, char **argv) { emit = true; } else if (argv[i] == "-mem2reg"s) { mem2reg = true; + } else if (argv[i] == "-gvn"s) { + gvn = true; + } else if (argv[i] == "-dump-json"s) { + dump_json = true; } else { if (input_path.empty()) { input_path = argv[i]; @@ -47,6 +55,8 @@ int main(int argc, char **argv) { } } } + if (gvn and not mem2reg) + LOG_WARNING << "Enabling GVN without mem2reg"; if (input_path.empty()) { print_help(argv[0]); return 0; @@ -77,12 +87,15 @@ int main(int argc, char **argv) { auto m = builder.getModule(); - m->set_print_name(); + // m->set_print_name(); PassManager PM(m.get()); if (mem2reg) { PM.add_pass(emit); } + if (gvn) + PM.add_pass(emit, dump_json); + PM.run(); auto IR = m->print(); @@ -95,11 +108,13 @@ int main(int argc, char **argv) { output_stream << IR; output_stream.close(); if (!emit) { - std::string lib_path = argv[0]; + std::string lib_path = " -L"s + argv[0]; auto idx = lib_path.rfind('/'); if (idx != std::string::npos) - lib_path.erase(lib_path.rfind('/')); - auto cmd_str = "clang -O0 -w "s + target_path + ".ll -o " + target_path + " -L" + lib_path + " -lcminus_io"; + lib_path.erase(idx); + else + lib_path.clear(); + auto cmd_str = "clang -O0 -w -no-pie "s + target_path + ".ll -o " + target_path + lib_path + " -lcminus_io"; int re_code0 = std::system(cmd_str.c_str()); cmd_str = "rm "s + target_path + ".ll"; int re_code1 = std::system(cmd_str.c_str()); @@ -108,5 +123,6 @@ int main(int argc, char **argv) { else return 1; } + return 0; } diff --git a/src/lightir/Value.cpp b/src/lightir/Value.cpp index 06297e142da28779ac2701c4cb762d959adb7929..0ccc6bde0f5487c57f3ed30b3b9f51bf58e9baf8 100644 --- a/src/lightir/Value.cpp +++ b/src/lightir/Value.cpp @@ -24,3 +24,17 @@ void Value::remove_use(Value *val) { auto is_val = [val](const Use &use) { return use.val_ == val; }; use_list_.remove_if(is_val); } + +void Value::replace_use_with_when(Value *new_val, std::function pred) { + for (auto it = use_list_.begin(); it != use_list_.end();) { + auto use = *it; + auto val = dynamic_cast(use.val_); + assert(val && "new_val is not a user"); + if (not pred(val)) { + ++it; + continue; + } + val->set_operand(use.arg_no_, new_val); + it = use_list_.erase(it); + } +} diff --git a/src/optimization/CMakeLists.txt b/src/optimization/CMakeLists.txt index 102c5e8a2afcdef2030817c359734b2fcd8c8090..8e969b3c6f1f340b763191a70970003afa904b9c 100644 --- a/src/optimization/CMakeLists.txt +++ b/src/optimization/CMakeLists.txt @@ -2,4 +2,5 @@ add_library( OP_lib STATIC Dominators.cpp Mem2Reg.cpp + GVN.cpp ) diff --git a/src/optimization/GVN.cpp b/src/optimization/GVN.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e10dda3b9c2c6801b99695fc72818080caf9bd7b --- /dev/null +++ b/src/optimization/GVN.cpp @@ -0,0 +1,306 @@ +#include "GVN.h" + +#include "BasicBlock.h" +#include "Constant.h" +#include "DeadCode.h" +#include "FuncInfo.h" +#include "Function.h" +#include "Instruction.h" +#include "logging.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace GVNExpression; +using std::string_literals::operator""s; +using std::shared_ptr; + +static auto get_const_int_value = [](Value *v) { return dynamic_cast(v)->get_value(); }; +static auto get_const_fp_value = [](Value *v) { return dynamic_cast(v)->get_value(); }; +// Constant Propagation helper, folders are done for you +Constant *ConstFolder::compute(Instruction *instr, Constant *value1, Constant *value2) { + auto op = instr->get_instr_type(); + switch (op) { + case Instruction::add: return ConstantInt::get(get_const_int_value(value1) + get_const_int_value(value2), module_); + case Instruction::sub: return ConstantInt::get(get_const_int_value(value1) - get_const_int_value(value2), module_); + case Instruction::mul: return ConstantInt::get(get_const_int_value(value1) * get_const_int_value(value2), module_); + case Instruction::sdiv: return ConstantInt::get(get_const_int_value(value1) / get_const_int_value(value2), module_); + case Instruction::fadd: return ConstantFP::get(get_const_fp_value(value1) + get_const_fp_value(value2), module_); + case Instruction::fsub: return ConstantFP::get(get_const_fp_value(value1) - get_const_fp_value(value2), module_); + case Instruction::fmul: return ConstantFP::get(get_const_fp_value(value1) * get_const_fp_value(value2), module_); + case Instruction::fdiv: return ConstantFP::get(get_const_fp_value(value1) / get_const_fp_value(value2), module_); + + case Instruction::cmp: + switch (dynamic_cast(instr)->get_cmp_op()) { + case CmpInst::EQ: return ConstantInt::get(get_const_int_value(value1) == get_const_int_value(value2), module_); + case CmpInst::NE: return ConstantInt::get(get_const_int_value(value1) != get_const_int_value(value2), module_); + case CmpInst::GT: return ConstantInt::get(get_const_int_value(value1) > get_const_int_value(value2), module_); + case CmpInst::GE: return ConstantInt::get(get_const_int_value(value1) >= get_const_int_value(value2), module_); + case CmpInst::LT: return ConstantInt::get(get_const_int_value(value1) < get_const_int_value(value2), module_); + case CmpInst::LE: return ConstantInt::get(get_const_int_value(value1) <= get_const_int_value(value2), module_); + } + case Instruction::fcmp: + switch (dynamic_cast(instr)->get_cmp_op()) { + case FCmpInst::EQ: return ConstantInt::get(get_const_fp_value(value1) == get_const_fp_value(value2), module_); + case FCmpInst::NE: return ConstantInt::get(get_const_fp_value(value1) != get_const_fp_value(value2), module_); + case FCmpInst::GT: return ConstantInt::get(get_const_fp_value(value1) > get_const_fp_value(value2), module_); + case FCmpInst::GE: return ConstantInt::get(get_const_fp_value(value1) >= get_const_fp_value(value2), module_); + case FCmpInst::LT: return ConstantInt::get(get_const_fp_value(value1) < get_const_fp_value(value2), module_); + case FCmpInst::LE: return ConstantInt::get(get_const_fp_value(value1) <= get_const_fp_value(value2), module_); + } + default: return nullptr; + } +} + +Constant *ConstFolder::compute(Instruction *instr, Constant *value1) { + auto op = instr->get_instr_type(); + switch (op) { + case Instruction::sitofp: return ConstantFP::get((float)get_const_int_value(value1), module_); + case Instruction::fptosi: return ConstantInt::get((int)get_const_fp_value(value1), module_); + case Instruction::zext: return ConstantInt::get((int)get_const_int_value(value1), module_); + default: return nullptr; + } +} + +namespace utils { +static std::string print_congruence_class(const CongruenceClass &cc) { + std::stringstream ss; + if (cc.index_ == 0) { + ss << "top class\n"; + return ss.str(); + } + ss << "\nindex: " << cc.index_ << "\nleader: " << cc.leader_->print() + << "\nvalue phi: " << (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_) + ss << member->print() << "; "; + ss << "}\n"; + return ss.str(); +} + +static std::string dump_cc_json(const CongruenceClass &cc) { + std::string json; + json += "["; + for (auto member : cc.members_) { + if (auto c = dynamic_cast(member)) + json += member->print() + ", "; + else + json += "\"%" + member->get_name() + "\", "; + } + json += "]"; + return json; +} + +static std::string dump_partition_json(const GVN::partitions &p) { + std::string json; + json += "["; + for (auto cc : p) + json += dump_cc_json(*cc) + ", "; + json += "]"; + return json; +} + +static std::string dump_bb2partition(const std::map &map) { + std::string json; + json += "{"; + for (auto [bb, p] : map) + json += "\"" + bb->get_name() + "\": " + dump_partition_json(p) + ","; + json += "}"; + return json; +} + +// logging utility for you +static void print_partitions(const GVN::partitions &p) { + if (p.empty()) { + LOG_DEBUG << "empty partitions\n"; + return; + } + std::string log; + for (auto &cc : p) + log += print_congruence_class(*cc); + LOG_DEBUG << log; // please don't use std::cout +} +} // namespace utils + +GVN::partitions GVN::join(const partitions &P1, const partitions &P2) { + // TODO: do intersection pair-wise + return {}; +} + +std::shared_ptr GVN::intersect(std::shared_ptr Ci, + std::shared_ptr Cj) { + // TODO + return {}; +} + +void GVN::detectEquivalences() { + bool changed = false; + // initialize pout with top + // iterate until converge + do { + // see the pseudo code in documentation + for (auto &bb : func_->get_basic_blocks()) { // you might need to visit the blocks in depth-first order + // get PIN of bb by predecessor(s) + // iterate through all instructions in the block + // and the phi instruction in all the successors + + // check changes in pout + } + } while (changed); +} + +shared_ptr GVN::valueExpr(Instruction *instr) { + // TODO + return {}; +} + +// instruction of the form `x = e`, mostly x is just e (SSA), 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 +GVN::partitions GVN::transferFunction(Instruction *x, Value *e, partitions pin) { + partitions pout = clone(pin); + // TODO: get different ValueExpr by Instruction::OpID, modify pout + return pout; +} + +shared_ptr GVN::valuePhiFunc(shared_ptr ve, const partitions &P) { + // TODO + return {}; +} + +shared_ptr GVN::getVN(const partitions &pout, shared_ptr ve) { + // TODO: return what? + for (auto it = pout.begin(); it != pout.end(); it++) + if ((*it)->value_expr_ and *(*it)->value_expr_ == *ve) + return {}; + return nullptr; +} + +void GVN::initPerFunction() { + next_value_number_ = 1; + pin_.clear(); + pout_.clear(); +} + +void GVN::replace_cc_members() { + for (auto &[_bb, part] : pout_) { + auto bb = _bb; // workaround: structured bindings can't be captured in C++17 + for (auto &cc : part) { + if (cc->index_ == 0) + continue; + // if you are planning to do constant propagation, leaders should be set to constant at some point + for (auto &member : cc->members_) { + bool member_is_phi = dynamic_cast(member); + bool value_phi = cc->value_phi_ != nullptr; + if (member != cc->leader_ and (value_phi or !member_is_phi)) { + // only replace the members if users are in the same block as bb + member->replace_use_with_when(cc->leader_, [bb](User *user) { + if (auto instr = dynamic_cast(user)) { + auto parent = instr->get_parent(); + auto &bb_pre = parent->get_pre_basic_blocks(); + if (instr->is_phi()) // as copy stmt, the phi belongs to this block + return std::find(bb_pre.begin(), bb_pre.end(), bb) != bb_pre.end(); + else + return parent == bb; + } + return false; + }); + } + } + } + } + return; +} + +// top-level function, done for you +void GVN::run() { + std::ofstream gvn_json; + if (dump_json_) { + gvn_json.open("gvn.json", std::ios::out); + gvn_json << "["; + } + + folder_ = std::make_unique(m_); + func_info_ = std::make_unique(m_); + func_info_->run(); + dce_ = std::make_unique(m_); + dce_->run(); // let dce take care of some dead phis with undef + + for (auto &f : m_->get_functions()) { + if (f.get_basic_blocks().empty()) + continue; + func_ = &f; + initPerFunction(); + LOG_INFO << "Processing " << f.get_name(); + detectEquivalences(); + LOG_INFO << "===============pin=========================\n"; + for (auto &[bb, part] : pin_) { + LOG_INFO << "\n===============bb: " << bb->get_name() << "=========================\npartitionIn: "; + for (auto &cc : part) + LOG_INFO << utils::print_congruence_class(*cc); + } + LOG_INFO << "\n===============pout=========================\n"; + for (auto &[bb, part] : pout_) { + LOG_INFO << "\n=====bb: " << bb->get_name() << "=====\npartitionOut: "; + for (auto &cc : part) + LOG_INFO << utils::print_congruence_class(*cc); + } + if (dump_json_) { + gvn_json << "{\n\"function\": "; + gvn_json << "\"" << f.get_name() << "\", "; + gvn_json << "\n\"pout\": " << utils::dump_bb2partition(pout_); + gvn_json << "},"; + } + replace_cc_members(); // don't delete instructions, just replace them + } + dce_->run(); // let dce do that for us + if (dump_json_) + gvn_json << "]"; +} + +template +static bool 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(&lhs)->equiv(static_cast(&rhs)); +} + +bool GVNExpression::operator==(const Expression &lhs, const Expression &rhs) { + if (lhs.get_expr_type() != rhs.get_expr_type()) + return false; + switch (lhs.get_expr_type()) { + case Expression::e_constant: return equiv_as(lhs, rhs); + case Expression::e_bin: return equiv_as(lhs, rhs); + case Expression::e_phi: return equiv_as(lhs, rhs); + } +} + +bool GVNExpression::operator==(const shared_ptr &lhs, const shared_ptr &rhs) { + if (lhs == nullptr and rhs == nullptr) // is the nullptr check necessary here? + return true; + return lhs and rhs and *lhs == *rhs; +} + +GVN::partitions GVN::clone(const partitions &p) { + partitions data; + for (auto &cc : p) { + data.insert(std::make_shared(*cc)); + } + return data; +} + +bool operator==(const GVN::partitions &p1, const GVN::partitions &p2) { + // TODO: how to compare partitions? + return false; +} + +bool CongruenceClass::operator==(const CongruenceClass &other) const { + // TODO: which fields need to be compared? + return false; +} diff --git a/tests/4-ir-opt/lab4_evals.py b/tests/4-ir-opt/lab4_evals.py new file mode 100755 index 0000000000000000000000000000000000000000..013ff9080cfc923544bb50118318c51d8426cbdb --- /dev/null +++ b/tests/4-ir-opt/lab4_evals.py @@ -0,0 +1,349 @@ +#!/usr/bin/env python3 +import subprocess +import os +import argparse +import re +import time +import glob +import json5 +from pathlib import Path + +# you can run the script from anywhere! +cminusfc_path = Path(__file__).absolute().parents[2] / "build/cminusfc" +cminusfc = str(cminusfc_path) + +try: + from tqdm import tqdm +except Exception as _: + os.system("apt install -y python3-tqdm || python3 -m pip install tqdm") + from tqdm import tqdm +repeated_time = 3 + + +def init_args(): + parser = argparse.ArgumentParser() + parser.add_argument("--GlobalValueNumber", "-gvn", action="store_true") + parser.add_argument( + "--GlobalValueNumberAnalysis", "-gvn-analysis", action="store_true" + ) + args = parser.parse_args() + return args + + +def get_raw_testcases(root_path): + file_names = glob.glob(root_path + "/*.cminus") + # pattern=r'[0-9]+' + # file_names.sort(key= lambda item:int(re.findall(pattern, os.path.basename(item))[0])) + return file_names + + +def get_baseline_files(root_path): + file_names = glob.glob(root_path + "/*.ll") + # pattern=r'[0-9]+' + # file_names.sort(key= lambda item:int(re.findall(pattern, os.path.basename(item))[0])) + return file_names + + +def compile_baseline_files(file_lists): + print("Compiling baseline files") + progess_bar = tqdm(total=len(file_lists), ncols=50) + exec_files = list() + for each in file_lists: + exec_file, _ = os.path.splitext(each) + COMMAND = "clang -O0 -w " + each + " -o " + exec_file + " -L. -lcminus_io" + try: + result = subprocess.run( + COMMAND, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + shell=True, + timeout=1, + ) + if result.returncode == 0: + exec_files.append(exec_file) + else: + exec_files.append(None) + print( + f"\nCompile {each.split('/')[-1]} \033[31;1m failed\033[0m") + except Exception as _: + exec_files.append(None) + print(f"Compile {each.split('/')[-1]} \033[31;1m failed\033[0m") + progess_bar.update(1) + progess_bar.close() + return exec_files + + +def compile_testcases(file_lists, option): + COMMAND = cminusfc + " " + option + " " + exec_files = list() + print("Compiling ", option) + progess_bar = tqdm(total=len(file_lists), ncols=50) + + for each in file_lists: + + try: + result = subprocess.run( + COMMAND + each, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + shell=True, + timeout=1, + ) + if result.returncode == 0: + exec_file, _ = os.path.splitext(each) + exec_files.append(exec_file) + else: + exec_files.append(None) + print( + f"\nCompile {each.split('/')[-1]} \033[31;1m failed\033[0m") + except Exception as _: + exec_files.append(None) + print(f"Compile {each.split('/')[-1]} \033[31;1m failed\033[0m") + + progess_bar.update(1) + progess_bar.close() + return exec_files + + +def gvn_evaluate(file_lists, metric_func, check_mode=True): + result = list() + print("Evalution ") + progess_bar = tqdm(total=len(file_lists), ncols=50) + for each in file_lists: + if each == None: + result.append(None) + continue + if check_if_correct(each, check_mode): + base = 0 + for _ in range(repeated_time): + re_value = metric_func(each) + if re_value != None: + base += re_value / repeated_time + else: + base = None + break + result.append(base) + else: + result.append(None) + + subprocess.call(["rm", "-rf", each]) + progess_bar.update(1) + progess_bar.close() + return result + + +def check_if_correct(exec_file, check_mode=True): + if check_mode: + input_option = None + if os.path.exists(exec_file + ".in"): + with open(exec_file + ".in", "rb") as fin: + input_option = fin.read() + try: + result = subprocess.run( + [exec_file], + input=input_option, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + timeout=10, + ) + with open(exec_file + ".out", "rb") as fout: + answer = fout.read() + if result.stdout == answer: + return True + else: + print( + f"Execute {exec_file.split('/')[-1]} result is not correct! your output:{result.stdout}, but the answer is:{answer}" + ) + return False + + except Exception as e: + print( + f"Execute {exec_file.split('/')[-1]} \033[31;1m failed\033[0m") + return False + else: + return True + + +def get_execute_time(exec_file): + try: + cmdline = "taskset -c 0 " + exec_file + " < " + exec_file + ".in" + " 2>&1" + input_option = None + start = time.time() + result = subprocess.run( + [cmdline], + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True, + timeout=10, + ) + elapsed = time.time() - start + return elapsed + except Exception as e: + print(f"Execute {exec_file.split('/')[-1]} \033[31;1m failed\033[0m") + return None + + +def table_print(testcase, before_optimization, after_optimization, baseline): + if len(before_optimization) == len(baseline) and len(before_optimization) == len( + after_optimization + ): + pass + else: + max_len = max( + [len(before_optimization), len(after_optimization), len(baseline)] + ) + if len(before_optimization) < max_len: + before_optimization += [None] * \ + (max_len - len(before_optimization)) + if len(after_optimization) < max_len: + after_optimization += [None] * (max_len - len(after_optimization)) + if len(baseline) < max_len: + baseline += [None] * (max_len - len(baseline)) + print( + "\033[33;1mtestcase", + "\t", + "\033[31;1mbefore optimization\033[0m", + "\t", + "\033[32;1mafter optimization\033[0m", + "\t", + "\033[35;1mbaseline\033[0m", + ) + for index, (result1, result2, result3) in enumerate( + zip(before_optimization, after_optimization, baseline) + ): + print( + testcase[index].split("/")[-1], + "\t\t%.2f" % result1 if result1 != None else "\t\tNone", + "\t\t\t%.2f" % result2 if result2 != None else "\t\t\tNone", + "\t\t %.2f" % result3 if result3 != None else "\t\t None", + ) + + +def calculate_gvn_bb_score(input_partition, answer_partition): + # score of every bb is either 0 or 1 + if len(input_partition) != len(answer_partition): + return 0 + score_cnt = 0 + for in_cc in input_partition: + for ans_cc in answer_partition: + if set(in_cc) == set(ans_cc): + score_cnt += 1 + break + if score_cnt == len(answer_partition): + return 1 + return 0 + + +def calculate_gvn_score(input_functions, answer_functions): + # input & answer is dict from json + # calculate score use sum(score of every bb)/total_bb + # score of every bb is either 1 or 0 + # total_bb is count of pout + + total_bb = 0 + for ans_func in answer_functions: + total_bb += len(ans_func["pout"]) + cal_score = 0 + for ans_func in answer_functions: + for in_func in input_functions: + if ans_func["function"] == in_func["function"]: + for ans_bb, ans_partition in ans_func["pout"].items(): + for in_bb, in_partition in in_func["pout"].items(): + if ans_bb == in_bb: + cal_score += calculate_gvn_bb_score( + in_partition, ans_partition + ) + else: + continue + else: + continue + return cal_score / total_bb + + +if __name__ == "__main__": + script_path = os.path.join(os.getcwd(), __file__) + usr_args = init_args() + + if usr_args.GlobalValueNumber: + print("=" * 10, "GlobalValueNumber", "=" * 10) + root_path = os.path.join( + os.path.dirname(script_path), "testcases/GVN/performance" + ) + testcases = get_raw_testcases(root_path=root_path) + exec_files1 = compile_testcases( + file_lists=testcases, option="-mem2reg") + results1 = gvn_evaluate(file_lists=exec_files1, + metric_func=get_execute_time) + + exec_files2 = compile_testcases( + file_lists=testcases, option="-mem2reg -gvn") + results2 = gvn_evaluate(file_lists=exec_files2, + metric_func=get_execute_time) + + baseline_files = get_baseline_files( + os.path.join(root_path, "baseline")) + exec_files3 = compile_baseline_files(baseline_files) + results3 = gvn_evaluate( + file_lists=exec_files3, metric_func=get_execute_time, check_mode=False + ) + table_print( + testcase=testcases, + before_optimization=results1, + after_optimization=results2, + baseline=results3, + ) + + if usr_args.GlobalValueNumberAnalysis: + print("=" * 10, "GlobalValueNumberAnalysis", "=" * 10) + root_path = os.path.join( + os.path.dirname(script_path), "testcases/GVN/functional" + ) + testcases = get_raw_testcases(root_path=root_path) + option = "-mem2reg -emit-llvm -gvn -dump-json" + COMMAND = cminusfc + " " + option + " " + print("Compiling ", option) + progess_bar = tqdm(total=len(testcases), ncols=50) + + score_list = [] + i = 0 + for each in testcases: + i += 1 + try: + result = subprocess.run( + COMMAND + each, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + shell=True, + timeout=1, + ) + if result.returncode == 0: + exec_file, _ = os.path.splitext(each) + each_base = each.split("/")[-1] + print(f"\nCompile {each_base} \033[32;1m success\033[0m") + with open("gvn.json", "r") as load_input: + with open(exec_file + ".json", "r") as load_answer: + print( + f"generate json {each_base} \033[32;1m success\033[0m" + ) + # here, input is a list of dict + input_functions = json5.load(load_input) + answer_functions = json5.load(load_answer) + score = calculate_gvn_score( + input_functions, answer_functions + ) + score_list.append((each_base, score)) + subprocess.call(["rm", "-rf", exec_file + ".ll"]) + else: + print( + f"\nnCompile {each.split('/')[-1]} \033[31;1m failed\033[0m") + except Exception as _: + print( + f"Analyze {each.split('/')[-1]} \033[31;1m failed\033[0m") + progess_bar.update(1) + progess_bar.close() + + i = 0 + for file, score in score_list: + i += 1 + print(file + ":", score) diff --git a/tests/4-ir-opt/testcases/GVN/functional/bin.cminus b/tests/4-ir-opt/testcases/GVN/functional/bin.cminus new file mode 100644 index 0000000000000000000000000000000000000000..e63686b7153405a4eba44d1ea1a80d203d5405e8 --- /dev/null +++ b/tests/4-ir-opt/testcases/GVN/functional/bin.cminus @@ -0,0 +1,19 @@ +/* c and d are redundant, and also check for constant propagation */ +int main(void) { + int a; + int b; + int c; + int d; + if (input() > input()) { + a = 33 + 33; + b = 44 + 44; + c = a + b; + } else { + a = 55 + 55; + b = 66 + 66; + c = a + b; + } + output(c); + d = a + b; + output(d); +} diff --git a/tests/4-ir-opt/testcases/GVN/functional/bin.json b/tests/4-ir-opt/testcases/GVN/functional/bin.json new file mode 100644 index 0000000000000000000000000000000000000000..40c9cc3e9db0e590465c5fa452937c430be170ad --- /dev/null +++ b/tests/4-ir-opt/testcases/GVN/functional/bin.json @@ -0,0 +1,109 @@ +[ + { + "function": "main", + "pout": { + "label_entry": [ + [ + "%op0", + ], + [ + "%op1", + ], + [ + "%op2", + ], + [ + "%op3", + ], + [ + "%op4", + ], + ], + "label5": [ + [ + "%op0", + ], + [ + "%op1", + ], + [ + "%op2", + ], + [ + "%op3", + ], + [ + "%op4", + ], + [ + "%op6", + "%op12", + ], + [ + "%op7", + "%op11", + ], + [ + "%op8", + "%op10", + ], + ], + "label9": [ + [ + "%op0", + ], + [ + "%op1", + ], + [ + "%op2", + ], + [ + "%op3", + ], + [ + "%op4", + ], + [ + "%op12", + ], + [ + "%op11", + ], + [ + "%op13", + "%op10", + ], + ], + "label14": [ + [ + "%op0", + ], + [ + "%op1", + ], + [ + "%op2", + ], + [ + "%op3", + ], + [ + "%op4", + ], + [ + "%op15", + "%op12", + ], + [ + "%op16", + "%op11", + ], + [ + "%op17", + "%op10", + ], + ], + } + }, +] \ No newline at end of file diff --git a/tests/4-ir-opt/testcases/GVN/functional/loop3.cminus b/tests/4-ir-opt/testcases/GVN/functional/loop3.cminus new file mode 100644 index 0000000000000000000000000000000000000000..2567a98116a43bc8c5905dd8bbe7e59a4fb1a0fb --- /dev/null +++ b/tests/4-ir-opt/testcases/GVN/functional/loop3.cminus @@ -0,0 +1,41 @@ +/* c and d are redundant, e and f are not redundant */ +int main(void) { + int i; + int j; + int a; + int b; + int c; + int d; + int e; + int f; + + a = 0; + b = 0; + c = 0; + d = 0; + e = 0; + f = 0; + + i = 1; + while (i < 10) { + j = 1; + a = input(); + b = input(); + c = a + b; + while (j < 10) { + i = i + j; + j = i + j; + c = a + b; + } + d = a + b; + i = i + 1; + + e = c + d; + output(c); + output(d); + } + f = c + d; + output(e); + output(f); + return 0; +} diff --git a/tests/4-ir-opt/testcases/GVN/functional/loop3.json b/tests/4-ir-opt/testcases/GVN/functional/loop3.json new file mode 100644 index 0000000000000000000000000000000000000000..a80a9bdb344c85bd063e4c2ee3a2fa3549ecb6b6 --- /dev/null +++ b/tests/4-ir-opt/testcases/GVN/functional/loop3.json @@ -0,0 +1,244 @@ +[ + { + "function": "main", + "pout": { + "label_entry": [ + [ + 0, + "%op3", + "%op2", + "%op1", + ], + [ + 1, + "%op7", + ], + ], + "label0": [ + [ + "%op3", + "%op2", + ], + [ + "%op1", + ], + [ + "%op7", + ], + [ + "%op8", + ], + [ + "%op9", + ], + [ + "%op10", + ], + ], + "label11": [ + [ + "%op3", + "%op2", + ], + [ + "%op1", + ], + [ + "%op20", + "%op7", + ], + [ + "%op8", + ], + [ + "%op9", + ], + [ + "%op10", + ], + [ + "%op12", + ], + [ + "%op13", + ], + [ + "%op14", + "%op18", + ], + [ + 1, + "%op19", + ], + ], + "label15": [ + [ + "%op3", + "%op2", + ], + [ + "%op1", + ], + [ + "%op7", + ], + [ + "%op8", + ], + [ + "%op9", + ], + [ + "%op10", + ], + [ + "%op16", + ], + ], + "label17": [ + [ + "%op12", + ], + [ + "%op13", + ], + [ + "%op3", + "%op2", + ], + [ + "%op1", + ], + [ + "%op8", + ], + [ + "%op9", + ], + [ + "%op10", + ], + [ + "%op7", + ], + [ + "%op20", + ], + [ + "%op14", + "%op18", + ], + [ + "%op19", + ], + [ + "%op21", + ], + [ + "%op22", + ], + [ + "%op23", + ], + ], + "label24": [ + [ + "%op12", + ], + [ + "%op13", + ], + [ + "%op3", + "%op2", + ], + [ + "%op1", + ], + [ + "%op8", + ], + [ + "%op9", + ], + [ + "%op10", + ], + [ + "%op7", + ], + [ + "%op14", + "%op27", + "%op18", + ], + [ + "%op21", + ], + [ + "%op22", + ], + [ + "%op23", + ], + [ + "%op25", + ], + [ + "%op26", + "%op19", + ], + [ + "%op20", + ], + ], + "label28": [ + [ + "%op12", + ], + [ + "%op13", + ], + [ + "%op8", + ], + [ + "%op9", + ], + [ + "%op10", + ], + [ + "%op20", + ], + [ + "%op14", + "%op29", + "%op3", + "%op18", + "%op2", + ], + [ + "%op19", + ], + [ + "%op21", + ], + [ + "%op22", + ], + [ + "%op23", + ], + [ + "%op30", + "%op7", + ], + [ + "%op31", + "%op1", + ], + ], + } + }, +] \ No newline at end of file diff --git a/tests/4-ir-opt/testcases/GVN/functional/pure_func.cminus b/tests/4-ir-opt/testcases/GVN/functional/pure_func.cminus new file mode 100644 index 0000000000000000000000000000000000000000..bd23964e0f3ea882b80197b6a5a747af85593038 --- /dev/null +++ b/tests/4-ir-opt/testcases/GVN/functional/pure_func.cminus @@ -0,0 +1,41 @@ +/* `max` is pure, can be detected using FuncInfo */ +int max(int a, int b) { + if (a > b) + return a; + return b; +} + +int a[10000]; +int b[10000]; + +void inputarray(int a[], int n) { + int i; + i = 0; + while (i < n) { + a[i] = input(); + i = i + 1; + } +} + +int main(void) { + int i; + int n; + n = input(); + inputarray(a, n); + inputarray(b, n); + i = 0; + while (i < n) { + int ai; + int bi; + /* `gep a i` and `max(ai,bi)` are redundant */ + ai = a[i]; + bi = b[i]; + a[i] = max(ai, bi) * max(ai, bi); + i = i + 1; + } + i = 0; + while (i < n) { + output(a[i]); + i = i + 1; + } +} \ No newline at end of file diff --git a/tests/4-ir-opt/testcases/GVN/functional/pure_func.json b/tests/4-ir-opt/testcases/GVN/functional/pure_func.json new file mode 100644 index 0000000000000000000000000000000000000000..6b1cf468c2f6dc091378531d5bff5934ac950de7 --- /dev/null +++ b/tests/4-ir-opt/testcases/GVN/functional/pure_func.json @@ -0,0 +1,863 @@ +[ + { + "function": "max", + "pout": { + "label_entry": [ + [ + "%arg0", + ], + [ + "%arg1", + ], + [ + "%a", + ], + [ + "%b", + ], + [ + "%op2", + ], + [ + "%op3", + ], + [ + "%op4", + ], + ], + "label5": [ + [ + "%arg0", + ], + [ + "%arg1", + ], + [ + "%a", + ], + [ + "%b", + ], + [ + "%op2", + ], + [ + "%op3", + ], + [ + "%op4", + ], + ], + "label6": [ + [ + "%arg0", + ], + [ + "%arg1", + ], + [ + "%a", + ], + [ + "%b", + ], + [ + "%op2", + ], + [ + "%op3", + ], + [ + "%op4", + ], + ], + } + }, + { + "function": "inputarray", + "pout": { + "label_entry": [ + [ + "%arg0", + ], + [ + "%arg1", + ], + [ + "%a", + ], + [ + "%b", + ], + [ + "%op4", + 0, + ], + ], + "label3": [ + [ + "%arg0", + ], + [ + "%arg1", + ], + [ + "%a", + ], + [ + "%b", + ], + [ + "%op4", + ], + [ + "%op5", + ], + [ + "%op6", + ], + [ + "%op7", + ], + ], + "label8": [ + [ + "%arg0", + ], + [ + "%arg1", + ], + [ + "%a", + ], + [ + "%b", + ], + [ + "%op4", + ], + [ + "%op5", + ], + [ + "%op6", + ], + [ + "%op7", + ], + [ + "%op9", + ], + [ + "%op10", + ], + ], + "label11": [ + [ + "%arg0", + ], + [ + "%arg1", + ], + [ + "%a", + ], + [ + "%b", + ], + [ + "%op4", + ], + [ + "%op5", + ], + [ + "%op6", + ], + [ + "%op7", + ], + ], + "label12": [ + [ + "%arg0", + ], + [ + "%arg1", + ], + [ + "%a", + ], + [ + "%b", + ], + [ + "%op4", + ], + [ + "%op5", + ], + [ + "%op6", + ], + [ + "%op7", + ], + [ + "%op9", + ], + [ + "%op10", + ], + ], + "label13": [ + [ + "%arg0", + ], + [ + "%arg1", + ], + [ + "%a", + ], + [ + "%b", + ], + [ + "%op5", + ], + [ + "%op6", + ], + [ + "%op7", + ], + [ + "%op9", + ], + [ + "%op10", + ], + [ + "%op14", + ], + [ + "%op4", + "%op15", + ], + ], + } + }, + { + "function": "main", + "pout": { + "label_entry": [ + [ + "%a", + ], + [ + "%b", + ], + [ + "%op0", + ], + [ + 0, + "%op6", + ], + [ + "%op1", + ], + [ + "%op2", + ], + ], + "label3": [ + [ + "%a", + ], + [ + "%b", + ], + [ + "%op0", + ], + [ + "%op1", + ], + [ + "%op2", + ], + [ + "%op6", + ], + [ + "%op7", + ], + [ + "%op8", + ], + [ + "%op9", + ], + ], + "label10": [ + [ + "%a", + ], + [ + "%b", + ], + [ + "%op0", + ], + [ + "%op1", + ], + [ + "%op2", + ], + [ + "%op6", + ], + [ + "%op7", + ], + [ + "%op8", + ], + [ + "%op9", + ], + [ + "%op11", + ], + ], + "label12": [ + [ + "%a", + ], + [ + "%b", + ], + [ + "%op0", + ], + [ + "%op1", + ], + [ + "%op2", + ], + [ + "%op6", + ], + [ + "%op7", + ], + [ + "%op8", + ], + [ + "%op9", + ], + [ + 0, + "%op31", + ], + ], + "label13": [ + [ + "%a", + ], + [ + "%b", + ], + [ + "%op0", + ], + [ + "%op1", + ], + [ + "%op2", + ], + [ + "%op6", + ], + [ + "%op7", + ], + [ + "%op8", + ], + [ + "%op9", + ], + [ + "%op11", + ], + ], + "label14": [ + [ + "%a", + ], + [ + "%b", + ], + [ + "%op0", + ], + [ + "%op1", + ], + [ + "%op2", + ], + [ + "%op6", + ], + [ + "%op7", + ], + [ + "%op8", + ], + [ + "%op9", + ], + [ + "%op11", + "%op17", + ], + [ + "%op15", + ], + [ + "%op16", + ], + ], + "label18": [ + [ + "%a", + ], + [ + "%b", + ], + [ + "%op0", + ], + [ + "%op1", + ], + [ + "%op2", + ], + [ + "%op6", + ], + [ + "%op7", + ], + [ + "%op8", + ], + [ + "%op9", + ], + [ + "%op11", + "%op17", + ], + [ + "%op15", + ], + [ + "%op16", + ], + ], + "label19": [ + [ + "%a", + ], + [ + "%b", + ], + [ + "%op0", + ], + [ + "%op1", + ], + [ + "%op2", + ], + [ + "%op6", + ], + [ + "%op7", + ], + [ + "%op8", + ], + [ + "%op9", + ], + [ + "%op11", + "%op17", + "%op25", + ], + [ + "%op15", + ], + [ + "%op16", + ], + [ + "%op20", + ], + [ + "%op21", + ], + [ + "%op22", + "%op23", + ], + [ + "%op24", + ], + ], + "label26": [ + [ + "%a", + ], + [ + "%b", + ], + [ + "%op0", + ], + [ + "%op1", + ], + [ + "%op2", + ], + [ + "%op6", + ], + [ + "%op7", + ], + [ + "%op8", + ], + [ + "%op9", + ], + [ + "%op11", + "%op17", + "%op25", + ], + [ + "%op15", + ], + [ + "%op16", + ], + [ + "%op20", + ], + [ + "%op21", + ], + [ + "%op22", + "%op23", + ], + [ + "%op24", + ], + ], + "label27": [ + [ + "%a", + ], + [ + "%b", + ], + [ + "%op0", + ], + [ + "%op1", + ], + [ + "%op2", + ], + [ + "%op7", + ], + [ + "%op8", + ], + [ + "%op9", + ], + [ + "%op11", + "%op17", + "%op25", + ], + [ + "%op15", + "%op28", + ], + [ + "%op16", + ], + [ + "%op20", + ], + [ + "%op21", + ], + [ + "%op22", + "%op23", + ], + [ + "%op24", + ], + [ + "%op6", + "%op29", + ], + ], + "label30": [ + [ + "%a", + ], + [ + "%b", + ], + [ + "%op0", + ], + [ + "%op1", + ], + [ + "%op2", + ], + [ + "%op6", + ], + [ + "%op7", + ], + [ + "%op8", + ], + [ + "%op9", + ], + [ + "%op31", + ], + [ + "%op32", + ], + [ + "%op33", + ], + [ + "%op34", + ], + ], + "label35": [ + [ + "%a", + ], + [ + "%b", + ], + [ + "%op0", + ], + [ + "%op1", + ], + [ + "%op2", + ], + [ + "%op6", + ], + [ + "%op7", + ], + [ + "%op8", + ], + [ + "%op9", + ], + [ + "%op31", + ], + [ + "%op32", + ], + [ + "%op33", + ], + [ + "%op34", + ], + [ + "%op36", + ], + ], + "label37": [ + [ + "%a", + ], + [ + "%b", + ], + [ + "%op0", + ], + [ + "%op1", + ], + [ + "%op2", + ], + [ + "%op6", + ], + [ + "%op7", + ], + [ + "%op8", + ], + [ + "%op9", + ], + [ + "%op31", + ], + [ + "%op32", + ], + [ + "%op33", + ], + [ + "%op34", + ], + ], + "label38": [ + [ + "%a", + ], + [ + "%b", + ], + [ + "%op0", + ], + [ + "%op1", + ], + [ + "%op2", + ], + [ + "%op6", + ], + [ + "%op7", + ], + [ + "%op8", + ], + [ + "%op9", + ], + [ + "%op31", + ], + [ + "%op32", + ], + [ + "%op33", + ], + [ + "%op34", + ], + [ + "%op36", + ], + ], + "label39": [ + [ + "%a", + ], + [ + "%b", + ], + [ + "%op0", + ], + [ + "%op1", + ], + [ + "%op2", + ], + [ + "%op6", + ], + [ + "%op7", + ], + [ + "%op8", + ], + [ + "%op9", + ], + [ + "%op32", + ], + [ + "%op33", + ], + [ + "%op34", + ], + [ + "%op36", + ], + [ + "%op40", + ], + [ + "%op41", + ], + [ + "%op31", + "%op42", + ], + ], + } + }, +] \ No newline at end of file diff --git a/tests/4-ir-opt/testcases/GVN/functional/single_bb1.cminus b/tests/4-ir-opt/testcases/GVN/functional/single_bb1.cminus new file mode 100644 index 0000000000000000000000000000000000000000..fca23c17f4634e32bce4ae1e65ea0e9b4f7c28b8 --- /dev/null +++ b/tests/4-ir-opt/testcases/GVN/functional/single_bb1.cminus @@ -0,0 +1,12 @@ +/* check GVN on one-block input */ +int main(void) { + int a; + int b; + /* we provide a pure function analysis, + input is not side-effect free, so multiple calls to input are considered different */ + a = input(); + b = input(); + output(a + b); + output(a + b); + return 0; +} diff --git a/tests/4-ir-opt/testcases/GVN/functional/single_bb1.json b/tests/4-ir-opt/testcases/GVN/functional/single_bb1.json new file mode 100644 index 0000000000000000000000000000000000000000..5ea2140bbed93871dfbb1979e7498e42ccf71688 --- /dev/null +++ b/tests/4-ir-opt/testcases/GVN/functional/single_bb1.json @@ -0,0 +1,4 @@ +[{ +"function": "main", +"pout": {"label_entry": [["%op0", ], ["%op1", ], ["%op2", "%op3", ], ], +}},] \ No newline at end of file diff --git a/tests/4-ir-opt/testcases/GVN/performance/baseline/const-prop.in b/tests/4-ir-opt/testcases/GVN/performance/baseline/const-prop.in new file mode 100644 index 0000000000000000000000000000000000000000..ea600cb61b366b723c6e331e118865642391327f --- /dev/null +++ b/tests/4-ir-opt/testcases/GVN/performance/baseline/const-prop.in @@ -0,0 +1 @@ +100000000 diff --git a/tests/4-ir-opt/testcases/GVN/performance/baseline/const-prop.ll b/tests/4-ir-opt/testcases/GVN/performance/baseline/const-prop.ll new file mode 100644 index 0000000000000000000000000000000000000000..d8a1c73fd46a244f877ec88e25f43278a3b77236 --- /dev/null +++ b/tests/4-ir-opt/testcases/GVN/performance/baseline/const-prop.ll @@ -0,0 +1,26 @@ +declare i32 @input() + +declare void @output(i32) + +declare void @outputFloat(float) + +declare void @neg_idx_except() + +define void @main() { +label_entry: + %op0 = call i32 @input() + br label %label1 +label1: ; preds = %label_entry, %label11 + %op2 = phi i32 [ 0, %label_entry ], [ 711082625, %label11 ] + %op7 = phi i32 [ 0, %label_entry ], [ %op37, %label11 ] + %op8 = icmp slt i32 %op7, %op0 + %op9 = zext i1 %op8 to i32 + %op10 = icmp ne i32 %op9, 0 + br i1 %op10, label %label11, label %label38 +label11: ; preds = %label1 + %op37 = add i32 %op7, 1 + br label %label1 +label38: ; preds = %label1 + call void @output(i32 %op2) + ret void +} diff --git a/tests/4-ir-opt/testcases/GVN/performance/baseline/transpose.in b/tests/4-ir-opt/testcases/GVN/performance/baseline/transpose.in new file mode 100644 index 0000000000000000000000000000000000000000..f37548b30c57768e9549667d431165587c834a8e --- /dev/null +++ b/tests/4-ir-opt/testcases/GVN/performance/baseline/transpose.in @@ -0,0 +1,32 @@ +10000000 +30 +2 +5 +4 +25 +8 +125 +16 +625 +32 +3125 +2 +5 +4 +25 +8 +125 +16 +625 +32 +3125 +2 +5 +4 +25 +8 +125 +16 +625 +32 +3125 diff --git a/tests/4-ir-opt/testcases/GVN/performance/baseline/transpose.ll b/tests/4-ir-opt/testcases/GVN/performance/baseline/transpose.ll new file mode 100644 index 0000000000000000000000000000000000000000..154e7f3f82312e5f0d391112a362643d1a7d729f --- /dev/null +++ b/tests/4-ir-opt/testcases/GVN/performance/baseline/transpose.ll @@ -0,0 +1,192 @@ +; ModuleID = 'cminus' +source_filename = "./test.cminus" + +@matrix = global [20000000 x i32] zeroinitializer +@ad = global [100000 x i32] zeroinitializer +@len = global i32 zeroinitializer +declare i32 @input() + +declare void @output(i32) + +declare void @outputFloat(float) + +declare void @neg_idx_except() + +define void @readarray() { +label_entry: + br label %label0 +label0: ; preds = %label_entry, %label11 + %op1 = phi i32 [ 0, %label_entry ], [ %op13, %label11 ] + %op2 = load i32, i32* @len + %op3 = icmp slt i32 %op1, %op2 + %op4 = zext i1 %op3 to i32 + %op5 = icmp ne i32 %op4, 0 + br i1 %op5, label %label6, label %label9 +label6: ; preds = %label0 + %op7 = call i32 @input() + %op8 = icmp slt i32 %op1, 0 + br i1 %op8, label %label10, label %label11 +label9: ; preds = %label0 + ret void +label10: ; preds = %label6 + call void @neg_idx_except() + ret void +label11: ; preds = %label6 + %op12 = getelementptr [100000 x i32], [100000 x i32]* @ad, i32 0, i32 %op1 + store i32 %op7, i32* %op12 + %op13 = add i32 %op1, 1 + br label %label0 +} +define i32 @transpose(i32 %arg0, i32* %arg1, i32 %arg2) { +label_entry: + %op4 = sdiv i32 %arg0, %arg2 + br label %label5 +label5: ; preds = %label_entry, %label25 + %op8 = phi i32 [ 0, %label_entry ], [ %op26, %label25 ] + %op9 = icmp slt i32 %op8, %op4 + %op10 = zext i1 %op9 to i32 + %op11 = icmp ne i32 %op10, 0 + br i1 %op11, label %label12, label %label13 +label12: ; preds = %label5 + br label %label15 +label13: ; preds = %label5 + ret i32 -1 +label15: ; preds = %label12, %label29 + %op17 = phi i32 [ 0, %label12 ], [ %op31, %label29 ] + %op18 = icmp slt i32 %op17, %arg2 + %op19 = zext i1 %op18 to i32 + %op20 = icmp ne i32 %op19, 0 + br i1 %op20, label %label21, label %label25 +label21: ; preds = %label15 + %op22 = icmp slt i32 %op8, %op17 + %op23 = zext i1 %op22 to i32 + %op24 = icmp ne i32 %op23, 0 + br i1 %op24, label %label27, label %label32 +label25: ; preds = %label15 + %op26 = add i32 %op8, 1 + br label %label5 +label27: ; preds = %label21 + %op28 = add i32 %op17, 1 + br label %label29 +label29: ; preds = %label27, %label57 + %op31 = phi i32 [ %op28, %label27 ], [ %op59, %label57 ] + br label %label15 +label32: ; preds = %label21 + %op33 = mul i32 %op8, %arg2 + %op34 = add i32 %op33, %op17 + %op35 = icmp slt i32 %op34, 0 + br i1 %op35, label %label36, label %label37 +label36: ; preds = %label32 + call void @neg_idx_except() + ret i32 0 +label37: ; preds = %label32 + %op38 = getelementptr i32, i32* %arg1, i32 %op34 + %op39 = load i32, i32* %op38 + br i1 %op35, label %label43, label %label44 +label43: ; preds = %label37 + call void @neg_idx_except() + ret i32 0 +label44: ; preds = %label37 + %op46 = load i32, i32* %op38 + %op47 = mul i32 %op17, %op4 + %op48 = add i32 %op47, %op8 + %op49 = icmp slt i32 %op48, 0 + br i1 %op49, label %label50, label %label51 +label50: ; preds = %label44 + call void @neg_idx_except() + ret i32 0 +label51: ; preds = %label44 + %op52 = getelementptr i32, i32* %arg1, i32 %op48 + store i32 %op46, i32* %op52 + br i1 %op35, label %label56, label %label57 +label56: ; preds = %label51 + call void @neg_idx_except() + ret i32 0 +label57: ; preds = %label51 + store i32 %op39, i32* %op38 + %op59 = add i32 %op17, 1 + br label %label29 +} +define i32 @main() { +label_entry: + %op0 = call i32 @input() + %op1 = call i32 @input() + store i32 %op1, i32* @len + call void @readarray() + br label %label2 +label2: ; preds = %label_entry, %label11 + %op3 = phi i32 [ 0, %label_entry ], [ %op13, %label11 ] + %op4 = icmp slt i32 %op3, %op0 + %op5 = zext i1 %op4 to i32 + %op6 = icmp ne i32 %op5, 0 + br i1 %op6, label %label7, label %label9 +label7: ; preds = %label2 + %op8 = icmp slt i32 %op3, 0 + br i1 %op8, label %label10, label %label11 +label9: ; preds = %label2 + br label %label14 +label10: ; preds = %label7 + call void @neg_idx_except() + ret i32 0 +label11: ; preds = %label7 + %op12 = getelementptr [20000000 x i32], [20000000 x i32]* @matrix, i32 0, i32 %op3 + store i32 %op3, i32* %op12 + %op13 = add i32 %op3, 1 + br label %label2 +label14: ; preds = %label9, %label25 + %op15 = phi i32 [ 0, %label9 ], [ %op29, %label25 ] + %op16 = load i32, i32* @len + %op17 = icmp slt i32 %op15, %op16 + %op18 = zext i1 %op17 to i32 + %op19 = icmp ne i32 %op18, 0 + br i1 %op19, label %label20, label %label23 +label20: ; preds = %label14 + %op21 = getelementptr [20000000 x i32], [20000000 x i32]* @matrix, i32 0, i32 0 + %op22 = icmp slt i32 %op15, 0 + br i1 %op22, label %label24, label %label25 +label23: ; preds = %label14 + br label %label30 +label24: ; preds = %label20 + call void @neg_idx_except() + ret i32 0 +label25: ; preds = %label20 + %op26 = getelementptr [100000 x i32], [100000 x i32]* @ad, i32 0, i32 %op15 + %op27 = load i32, i32* %op26 + %op28 = call i32 @transpose(i32 %op0, i32* %op21, i32 %op27) + %op29 = add i32 %op15, 1 + br label %label14 +label30: ; preds = %label23, %label45 + %op31 = phi i32 [ 0, %label23 ], [ %op49, %label45 ] + %op32 = phi i32 [ 0, %label23 ], [ %op50, %label45 ] + %op33 = load i32, i32* @len + %op34 = icmp slt i32 %op32, %op33 + %op35 = zext i1 %op34 to i32 + %op36 = icmp ne i32 %op35, 0 + br i1 %op36, label %label37, label %label40 +label37: ; preds = %label30 + %op38 = mul i32 %op32, %op32 + %op39 = icmp slt i32 %op32, 0 + br i1 %op39, label %label44, label %label45 +label40: ; preds = %label30 + %op41 = icmp slt i32 %op31, 0 + %op42 = zext i1 %op41 to i32 + %op43 = icmp ne i32 %op42, 0 + br i1 %op43, label %label51, label %label53 +label44: ; preds = %label37 + call void @neg_idx_except() + ret i32 0 +label45: ; preds = %label37 + %op46 = getelementptr [20000000 x i32], [20000000 x i32]* @matrix, i32 0, i32 %op32 + %op47 = load i32, i32* %op46 + %op48 = mul i32 %op38, %op47 + %op49 = add i32 %op31, %op48 + %op50 = add i32 %op32, 1 + br label %label30 +label51: ; preds = %label40 + %op52 = sub i32 0, %op31 + br label %label53 +label53: ; preds = %label40, %label51 + %op54 = phi i32 [ %op31, %label40 ], [ %op52, %label51 ] + call void @output(i32 %op54) + ret i32 0 +} diff --git a/tests/4-ir-opt/testcases/GVN/performance/const-prop.cminus b/tests/4-ir-opt/testcases/GVN/performance/const-prop.cminus new file mode 100644 index 0000000000000000000000000000000000000000..93d726f539fbb965d46b3c44f17ac8b1755740d7 --- /dev/null +++ b/tests/4-ir-opt/testcases/GVN/performance/const-prop.cminus @@ -0,0 +1,23 @@ +void main(void) { + int c; + int a; + int b; + int d; + int f; + int g; + int loopCnt; + loopCnt = input(); + c = 0; + a = 0; + g = 0; + while (c < loopCnt) { + a = 1.23456 * 5.73478 * 2.3333 * 4.3673 * 6.34636; + b = a * a * a * a * a * a; + d = b * b * b * b * b * b; + f = d * d * d * d * d * d; + g = f * f * f * f * f * f; + c = c + 1; + } + output(g); + return; +} \ No newline at end of file diff --git a/tests/4-ir-opt/testcases/GVN/performance/const-prop.in b/tests/4-ir-opt/testcases/GVN/performance/const-prop.in new file mode 100644 index 0000000000000000000000000000000000000000..ea600cb61b366b723c6e331e118865642391327f --- /dev/null +++ b/tests/4-ir-opt/testcases/GVN/performance/const-prop.in @@ -0,0 +1 @@ +100000000 diff --git a/tests/4-ir-opt/testcases/GVN/performance/const-prop.out b/tests/4-ir-opt/testcases/GVN/performance/const-prop.out new file mode 100644 index 0000000000000000000000000000000000000000..79f74f4d2fc0b5c963f85bb11827c88073a3733b --- /dev/null +++ b/tests/4-ir-opt/testcases/GVN/performance/const-prop.out @@ -0,0 +1 @@ +711082625 diff --git a/tests/4-ir-opt/testcases/GVN/performance/transpose.cminus b/tests/4-ir-opt/testcases/GVN/performance/transpose.cminus new file mode 100644 index 0000000000000000000000000000000000000000..0aff64fd0996534f1612c3203c41dbf26b9a9812 --- /dev/null +++ b/tests/4-ir-opt/testcases/GVN/performance/transpose.cminus @@ -0,0 +1,70 @@ +int matrix[20000000]; +int ad[100000]; + +int len; + +void readarray(void) { + int cnt; + cnt = 0; + while (cnt < len) { + ad[cnt] = input(); + cnt = cnt + 1; + } +} + +int transpose(int n, int matrix[], int rowsize) { + int colsize; + int i; + int j; + int curr; + colsize = n / rowsize; + i = 0; + j = 0; + while (i < colsize) { + j = 0; + while (j < rowsize) { + if (i < j) { + j = j + 1; + } else { + curr = matrix[i * rowsize + j]; + matrix[j * colsize + i] = matrix[i * rowsize + j]; + matrix[i * rowsize + j] = curr; + j = j + 1; + } + } + i = i + 1; + } + return 0 - 1; +} + +int main(void) { + int n; + int i; + int ans; + n = input(); + len = input(); + readarray(); + i = 0; + + while (i < n) { + matrix[i] = i; + i = i + 1; + } + i = 0; + while (i < len) { + transpose(n, matrix, ad[i]); + i = i + 1; + } + + ans = 0; + i = 0; + while (i < len) { + ans = ans + i * i * matrix[i]; + i = i + 1; + } + if (ans < 0) { + ans = 0 - ans; + } + output(ans); + return 0; +} diff --git a/tests/4-ir-opt/testcases/GVN/performance/transpose.in b/tests/4-ir-opt/testcases/GVN/performance/transpose.in new file mode 100644 index 0000000000000000000000000000000000000000..f37548b30c57768e9549667d431165587c834a8e --- /dev/null +++ b/tests/4-ir-opt/testcases/GVN/performance/transpose.in @@ -0,0 +1,32 @@ +10000000 +30 +2 +5 +4 +25 +8 +125 +16 +625 +32 +3125 +2 +5 +4 +25 +8 +125 +16 +625 +32 +3125 +2 +5 +4 +25 +8 +125 +16 +625 +32 +3125 diff --git a/tests/4-ir-opt/testcases/GVN/performance/transpose.out b/tests/4-ir-opt/testcases/GVN/performance/transpose.out new file mode 100644 index 0000000000000000000000000000000000000000..006d9b56fdab13cdcc3256d4da57476307ee6049 --- /dev/null +++ b/tests/4-ir-opt/testcases/GVN/performance/transpose.out @@ -0,0 +1 @@ +1042523985