Skip to content

Commit

Permalink
update contest docs
Browse files Browse the repository at this point in the history
  • Loading branch information
Ksitta committed Sep 5, 2024
1 parent 573a7d4 commit a2fce17
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 64 deletions.
15 changes: 8 additions & 7 deletions SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,14 @@

## 大实验参考文档

* 大实验(文档正在编写中)
* [大实验简介](docs/contest/intro.md)
* [中端设计](docs/contest/midend/midend.md)
* [静态单赋值](docs/contest/midend/ssa.md)
* [复写传播](docs/contest/midend/rp.md)
* [常量传播](docs/contest/midend/cp.md)
* [死代码消除](docs/contest/midend/dce.md)
* [大实验简介](docs/contest/intro.md)
* [前端设计](docs/contest/frontend.md)
* [中端设计](docs/contest/midend/midend.md)
* [静态单赋值](docs/contest/midend/ssa.md)
* [复写传播](docs/contest/midend/rp.md)
* [常量传播](docs/contest/midend/cp.md)
* [死代码消除](docs/contest/midend/dce.md)
* [后端设计](docs/contest/backend.md)

## 参考资料

Expand Down
26 changes: 26 additions & 0 deletions docs/contest/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,29 @@

## 评分方法

评分有两个选项:

- 选项一 完成竞赛第二阶段的优化编译器,替代期末考试

成绩占比 90%,剩余 10% 为书面作业和日常成绩。

你需要通过 stage 1 - 6 的所有测试样例以及附加测试的测试样例,这样你可以获得50%的正确性得分。剩下的40%的得分将根据你的编译器的性能进行评分。

性能评分方案:
附加测试中`performance`部分测试样例,以gcc打开`-O2`优化的性能的 60% 为满分,按照比例折算。如果一个程序gcc编译后运行时间为12s,如果你的程序执行时间为20s即为满分。

你的单个测试点的得分为:

$$
\min\{100, 100 * GCC编译程序运行时间 * 1.25/你的程序运行时间\}
$$

所有测试点取**几何平均值**,最后结果 * 40% 作为你的最终性能测试成绩。

- 选项二 仅完成竞赛第一阶段(达到课程基础实验的要求)

实验部分占比与基础实验一致,你不需要完成思考题,但是需要简单介绍你的编译器是怎么完成每一个 step 的。根据通过测试样例情况评分。

- 完成 stage 1 - 5 实验成绩 35% ,书面作业和日常成绩10% ,期末成绩 55%。
- 完成 stage 1 - 6 实验成绩 42% ,书面作业和日常成绩10% ,期末成绩 48%。
- 完成 stage 1 - 7 实验成绩 50% ,书面作业和日常成绩10% ,期末成绩 40%。
35 changes: 0 additions & 35 deletions docs/contest/midend/cp.md
Original file line number Diff line number Diff line change
@@ -1,36 +1 @@
# 常量传播
<!--
## 原理
常量传播的目的在于发掘代码中可能存在的常量,尽量用对常量的引用替代对虚拟寄存器的引用(虚拟寄存器和变量是同一个概念,以下都使用变量),并尽量计算出可以计算的常量表达式。
为了实现常量传播,类比于前两种数据流,不难想象常量传播的数据流是"可用常量分析",找出在一条语句处有哪些对变量赋的常量值可以到达。每一条涉及到赋值的语句处,杀死之前与左端项相关的赋值,如果右端项为变量则生成对左端项的变量赋值;反之如果对左端项赋予了一个常量值或者常量表达式,则生成对左端项的常量赋值。
形式化地,常量传播的值集是$R \rightarrow V$的函数的集合。其中$R$是变量,$V$是集合$变量的所有可能取值 \cup \{NAC, UNDEF\}$,它用来表示变量在一个程序点处的取值。其中$NAC$表示已知它的值不是一个常量;$UNDEF$表示不知道它的取值,出现$UNDEF$是因为这个程序点处这个变量尚未赋值。
> 如果你觉得函数太抽象了,可以认为值集就是一个元素类型为$V$的数组,用变量的id为下标访问,实际上也是这么实现的。
>
> 这个数据流和其他数据流有一个很大的区别:它的值集的大小是无限的。不过这并不影响数据流分析算法的收敛性,感兴趣的同学可以自己查阅相关的资料。
单条语句的传递函数$f_S$定义为:设$m' = f_S(m)$(注意$m$和$m'$都属于值集,所以它们都是函数),对于语句$S$:
- 如果$S$没有给任何变量赋值,则$m' = m$
- 否则,设$S$对变量$x$赋值了(假设一条语句最多只能给一个变量赋值),则$m'(y) = m(y), \forall y \in R, y \ne x$,此外:
- 如果赋值的右端项是常数$c$,则$m'(x) = c$
- 如果赋值的右端项是表达式$y \oplus z$($\oplus$是任意一个运算符,也不仅局限于两个运算数的情况,单目运算和复写语句都可以归于此类,规则是类似的)则:
- 如果$m(y)$和$m(z)$都是常数,则$m'(x) = m(y) \oplus m(z)$
- 如果$m(y)$和$m(z)$中有一个是$NAC$,则$m'(x) = NAC$
- 否则,$m'(x) = UNDEF$
- 否则,$m'(x) = NAC$,这里可能包括赋值的右端项是函数的返回值,是访存的结果之类的,在简单的优化器中都直接认为这些结果不是常数
当两个基本块交汇的时候,对于每个变量,需要考虑以下几种情况:
- 如果两个基本块末尾处它都是常量,且两个常量值相等,则交汇结果为这个常量
- 如果一个基本块末尾处它是常量,另一个基本块结尾处它是$UNDEF$,则交汇结果为**这个常量**
- 一旦出现了这样的情形,可能意味着后续程序中有使用未赋初值就使用一个变量的行为,优化器可以依据"未赋初值的变量可以有任何取值",认为这个取值就是另一个基本块的常量值,从而就回到了前一种情形
- 如果两个基本块末尾处它都是$UNDEF$,则交汇结果为$UNDEF$
- 否则,交汇结果为$NAC$ -->

<!-- 这个交汇操作用格图表示如下: -->
<!--
![int_lattice](./pic/int_lattice.png) -->
22 changes: 0 additions & 22 deletions docs/contest/midend/dce.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,6 @@
死代码消除即无用代码消除,死代码和不可达代码是两个概念。前者指的是执行之后没有任何作用的代码(例如:多余的计算),后者指的是永远无法被执行到的代码。

活跃变量分析为每个程序点计算出所有变量的集合的子集,用于表示该点处活跃的变量,所以数据流分析的值集为所有变量的集合的幂集。"活跃"的含义是在程序执行过这一点**之后**,这个变量**当前的值**会被使用到,所以数据流分析是后向的。对于单个语句$S$,传递函数要根据$S$之后活跃的变量计算$S$之前活跃的变量,计算方法为:所有$S$用到的变量在$S$之前都是活跃的,所有$S$之后活跃的变量,如果没有在$S$中被定值,证明未来的那次使用用的还是$S$之前的值,所以也是活跃的。
<!--
综合得,传递函数定义为:$f_S(x) = (x - def_S) \cup use_S$。其中 $def_S$ 是$S$中定值的所有变量的集合,$use_S$是$S$中使用的所有变量的集合。
基本块$B$的传递函数定义为:
$$
f_B(x) = f_{S_1}(...f_{S_{n - 1}}(f_{S_n}(x))) \\
= (((((x - def_{S_n}) \cup use_{S_n}) - def_{S_{n - 1}}) \cup use_{S_{n - 1}} ...) - def_{S_1}) \cup use_{S_1}\\
\overset{\mathrm{数学归纳法}}{=} (x - \bigcup_{i = 1}^n def_{S_i}) \cup \bigcup_{i = 1}^n (use_{S_i} - \bigcup_{j = 1}^{i - 1} def_{S_j})
$$
最后一个等号使用数学归纳法来证明,读者自证不难。
定义$def_B = \bigcup_{i = 1}^n def_{S_i}$,$use_B = \bigcup_{i = 1}^n (use_{S_i} - \bigcup_{j = 1}^{i - 1} def_{S_j})$,这样上面的式子就是大家熟悉的形式了。那么这个形式和课堂上定义的$LiveUse$和$Def$是一致的吗?
![](pic/aliveness.png)
先看$use_B$和$LiveUse$,$LiveUse$为$B$中定值之前被引用的变量的集合,而$use_B$定义中每一个求并项都是一条语句的$use$集合减去在这条语句前面的所有$def$集,也就是说如果一个变量在某条语句中被使用了,而且没有在这条语句之前的任何一条语句被定值,那么它属于$use_B$,此外都不属于。显然,这与$LiveUse$的定义是符合的。
再看$def_B$和$Def$,其实很容易可以看出这两个集合并不相同,$def_B$包含了被定值的所有变量,而$Def$要求定值之前没有引用过,所以$Def \subseteq def_B$。然而这个区别不会影响任何计算结果:如果变量$x$满足$x \in def_B, x \notin Def$,则意味着它定值之前被引用过,则$x \in use_B, x \in LiveUse$,则它一定在这一步的结果集合中。
> 所以,$Def$这样的定义是冗余的,只会加大计算$Def$时的计算量,使用$def_B$的定义就会简单一些,而且不会影响最终结果。 -->

这里讲一下几个常见的需要注意的点:

Expand Down

0 comments on commit a2fce17

Please sign in to comment.