Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 32 additions & 2 deletions 30-optimize.tex
Original file line number Diff line number Diff line change
Expand Up @@ -302,8 +302,8 @@ \subsection{支配关系的确定}
通过节点之间的必经节点关系,我们就可以依此来建立一颗支配树。在支配树中,节点 a 是 b 的父亲,当且仅当 a 是 b 的最近必经节点。

\item[支配边界 (Dominance Frontier)]
Y 是 X 的支配边界,当且仅当 X 是 Y 的一个前驱节点(在 CFG 中)P 的必经节点,并且 X 并不是 Y 的严格必经节点。请看上图之例。比如,B 是 E 的前驱节点,同时 B 是 B 的必经节点,并且 B 不是 E 的严格必经节点。因此,E 是 B 的支配边界。从直觉上去考察这件事,支配边界实质上在表征到达这个点后,数据流可能会发生和其他数据流合并的情况。
\begin{figure}
Y 是 X 的支配边界,当且仅当 X 是 Y 的一个前驱节点(在 CFG 中)P 的必经节点,并且 X 并不是 Y 的严格必经节点。请看下图之例。比如,B 是 E 的前驱节点,同时 B 是 B 的必经节点,并且 B 不是 E 的严格必经节点。因此,E 是 B 的支配边界。从直觉上去考察这件事,支配边界实质上在表征到达这个点后,数据流可能会发生和其他数据流合并的情况。
\begin{figure}[h]
\centering
\includegraphics[width=0.5\linewidth]{CTG.jpg}
\caption{控制流图}
Expand Down Expand Up @@ -724,3 +724,33 @@ \subsection{图染色流程} \label{opt-graph-workflow}

\item 如果存在溢出的变量,则重写相关代码(章节 \ref{opt-graph-rewrite}),注意重写完需要再次进行图染色。
\end{enumerate}

\section{寄存器分配:线性扫描算法}
当你读完上面的图染色算法之后,有没有感到一头雾水,有没有感到无所适从?诚然,图染色算法分配寄存器的质量比较高,但是该算法时间复杂度较高,同时代码本身也较为复杂。为此,在这里谨介绍另外一种常见的寄存器分配算法:线性扫描算法。这种算法的时间复杂度理论上来说是线性的,同时编码较为简单。尽管其寄存器分配的质量较图染色算法稍差一些,但最坏情况下,质量下降不会超过10\%。目前,llvm中的basic寄存器分配策略,和greedy寄存器分配策略,基本是在线性扫描算法的基础上改进得来的。

线性扫描算法的思路是非常符合直觉的。首先,我们确定每条语句的线性序。说人话,就是给每条语句指派一个唯一的编号,使得每条语句之间的编号都有严格的大小关系。然后,我们通过活跃分析算法,计算得到每个变量的“活跃区间”:即该变量仅在编号为$[a, b]$的语句中活跃。这个区间越精确越好。然后,我们将这些区间按照左端点的大小进行排序,从而构建一优先级队列,队头为左端点最少的一元素。最后,我们不断对该优先级队列执行出队操作。并且检查当前是否有寄存器空闲,并尝试分配之。否则,则将其溢出至栈上。

\subsection{确定语句线性序}
\begin{figure}[h]
\centering
\includegraphics[width=0.5\linewidth]{CTG.jpg}
\caption{控制流图}
\label{fig:enter-label}
\end{figure}
我们再以这张控制流图为例,来介绍确定语句线性序的算法。根据论文中的做法,我们从CTG的入口开始执行DFS,将每个结点都访问一次,在\textbf{退出}某一个节点时,输出该变量。那么,我们可以轻易得到这样的dfs顺序:$A\to B, B\to D, B\to E, E\to F, A\to C$,得到的输出结果是$D, F, E, B, C, A$。
然后,我们将其逆过来,即得到各基本块之间的线性序,即$A, C, B, E, F, D$。这就是基本块之间的线性序关系了。接下来,我们要做的无非是在基本块内对每一句语句进行标号了。这是平凡的。

\subsection{确定活跃区间}
这一部分请见活跃分析章节。我们只需要记录下每条语句处的活跃变量,根据每条语句的标号,对应地去更新这些变量的活跃变量即可。

\subsection{活跃区间排序}
\sout{不会吧不会吧,不会还有人优先级队列也不会用吧?真是杂鱼呢$\sim$}
这一部分的工作是非常平凡的。我们可以创造一个区间类,挑选一个心仪的容器,重载比较运算符,或者是用仿函数的方式确定区间类之间的大小关系进行排序即可。

\subsection{寄存器分配}
正如在该部分总述中的做法,不断对优先级队列执行出队操作,并检查是否有寄存器空闲:即该寄存器从来没有被指派过变量,或者指派给该寄存器之变量的活跃区间右端点小于当前分配变量的活跃区间之左端点。若有,则将当前变量分配给空闲的寄存器。如果没有,则将其溢出至栈上。

\subsection{总结}
不难发现,线性扫描算法是一种非常简洁,明确的寄存器分配算法。此外,如果向对线性扫描算法执行一定的优化,可以考虑进行区间拆分等操作,或是探索更合适的线性排序方法,或者可以考虑如何更好地对活跃区间排序等,于此不复赘述。

让我们动手吧!
7 changes: 4 additions & 3 deletions main.tex
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
\subtitle{ACM班}

\author{}
\institute{ACM班编译器助教组}
\date{2023/07/02}
\version{0.9.0}
\institute{ACM班编译器助教组^*}
\date{2024/09/05}
\version{0.10.0}
% \bioinfo{自定义}{信息}

% \extrainfo{注意:本模板自 2023 年 1 月 1 日开始,不再更新和维护!}
Expand All @@ -20,6 +20,7 @@

% 本文档命令
\usepackage{array}
\usepackage{ulem}
\newcommand{\ccr}[1]{\makecell{{\color{#1}\rule{1cm}{1cm}}}}

% 修改标题页的橙色带
Expand Down