From 8df20d59a74cadd4820014cbaa8e82551097bd01 Mon Sep 17 00:00:00 2001 From: cgt <18760564611@163.com> Date: Thu, 15 Aug 2024 18:31:30 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A0=B9=E6=8D=AE=E4=BF=AE=E6=94=B9=E6=84=8F?= =?UTF-8?q?=E8=A7=81=E4=BF=AE=E6=94=B9=E4=B9=A6=E7=A8=BF=E5=86=85=E5=AE=B9?= =?UTF-8?q?=EF=BC=88ConstantFold=EF=BC=8CCSE=EF=BC=8CDCE=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 03Compiler/03Frontend/07ConstantFold.md | 2 +- 03Compiler/03Frontend/08CSE.md | 4 ++-- 03Compiler/03Frontend/09DCE.md | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/03Compiler/03Frontend/07ConstantFold.md b/03Compiler/03Frontend/07ConstantFold.md index 9b47e1d0..fcf09a23 100644 --- a/03Compiler/03Frontend/07ConstantFold.md +++ b/03Compiler/03Frontend/07ConstantFold.md @@ -35,7 +35,7 @@ dis.dis("day_sec=24*60*60") 上述的 CPython 的字节码表明,python 在对 day_sec 赋值是直接加载计算结果 86400,相比于 3 次载入数据和两次乘法,更加地高效。 -表达式 e 可进行常量折叠,当且仅当表达式 e 的所有子表达式都是常量。而子表达式被判断为常量通常需要常量传播的帮助。 +表达式可进行常量折叠,当且仅当表达式的所有子表达式都是常量。而子表达式被判断为常量通常需要常量传播的帮助。 举个例子: diff --git a/03Compiler/03Frontend/08CSE.md b/03Compiler/03Frontend/08CSE.md index 3c37977d..1610752e 100644 --- a/03Compiler/03Frontend/08CSE.md +++ b/03Compiler/03Frontend/08CSE.md @@ -150,7 +150,7 @@ $$ - 如果 $b_i$ 只有一个后继节点 $b_j$,则将 $Insert(i,j)$ 中的表达式插入到 $b_i$ 的出口处。 - 如果 $b_j$ 只有一个前趋节点 $b_i$,则将 $Insert(i,j)$ 中的表达式插入到 $b_j$ 的入口处。 -- 若前两个条件都不满足,则在边 {i->j} 上新建一个基本块,将 $Insert(i,j)$ 中的表达式插入到该基本块中。 +- 若前两个条件都不满足,则在边 ${b_i\rightarrow b_j}$ 上新建一个基本块,将 $Insert(i,j)$ 中的表达式插入到该基本块中。 需要注意的是,由于插入的是新的赋值语句,所以插入后,有一些表达式冗余了。比如对于一个基本块的向上展示的表达式。因此编译器对每个基本块都需要维护一个删除集合,记为 $Delete(i)$。其定义如下: @@ -166,7 +166,7 @@ AI 编译器中公共子表达式消除采取相同的思路,区别在于 AI ![second](images/08CSE02.png) -图中 Op3 和 Op4 都经过了相同的图结构{{Op1,Op2},{Op1->Op2}}, AI 编译器会将相同子图的所有不同输出都连接到同一个子图上,然后会在后续的死代码消除中删除其他相同的子图,从而达到简化计算图的目的。减少计算开销。 +图中 Op3 和 Op4 都经过了相同的图结构$\{\{Op1,Op2\},Op1\rightarrow Op2\}$, AI 编译器会将相同子图的所有不同输出都连接到同一个子图上,然后会在后续的死代码消除中删除其他相同的子图,从而达到简化计算图的目的。减少计算开销。 ## 公共子表达式实现案例 diff --git a/03Compiler/03Frontend/09DCE.md b/03Compiler/03Frontend/09DCE.md index 6135dce8..158d7fba 100644 --- a/03Compiler/03Frontend/09DCE.md +++ b/03Compiler/03Frontend/09DCE.md @@ -119,15 +119,15 @@ $$ ![控制依赖](images/09DCE01.png) -如上图,$B_4$ 和 $B_5$ 的控制依赖点为 $B_3$ ,以 $B_4$ 为例,从 $B_3$ 到出口节点 $B_8$,存在非空路径{$B_3$->$B_4$->$B_6$->$B_8$}经过 $B_4$,又存在非空路径{$B_3$->$B_5$->$B_6$->$B_8$}不经过 $B_4$,所以 $B_4$ 的控制依赖点为 $B_3$ , $B_5$ 同理。 +如上图,$B_4$ 和 $B_5$ 的控制依赖点为 $B_3$ ,以 $B_4$ 为例,从 $B_3$ 到出口节点 $B_8$,存在非空路径$\{B_3\rightarrow B_4\rightarrow B_6\rightarrow B_8\}$经过 $B_4$,又存在非空路径$\{B_3\rightarrow B_5\rightarrow B_6\rightarrow B_8\}$不经过 $B_4$,所以 $B_4$ 的控制依赖点为 $B_3$ , $B_5$ 同理。 -在传统编译器中,对于某个关键操作 i ,假设 i:v <- x op y,如果操作 x 和 y 的定义没有被标记上有用,则将其标记为有用操作。然后遍历关机操作 d 所在块的控制依赖块,将控制依赖块的条件转移操作标记为有用。在标记操作完成后,需要将无用的条件转移操作转化为跳转操作,跳转到该基本块的第一个含有用操作的后支配节点。因为当一个基本块的条件转移操作没有被标记为有用,那么从其后继结点一直到其直接的后向支配者结点,都不包含有用操作。如果其直接后继节点不包含有用的操作,也是同样。由于出口节点是一定含有有用操作,所以上述操作在向后查找的过程中,一定会停止。 +在传统编译器中,对于某个关键操作 i ,假设 i:v = x op y,如果操作 x 和 y 的定义没有被标记上有用,则将其标记为有用操作。然后遍历关机操作 d 所在块的控制依赖块,将控制依赖块的条件转移操作标记为有用。在标记操作完成后,需要将无用的条件转移操作转化为跳转操作,跳转到该基本块的第一个含有用操作的后支配节点。因为当一个基本块的条件转移操作没有被标记为有用,那么从其后继结点一直到其直接的后向支配者结点,都不包含有用操作。如果其直接后继节点不包含有用的操作,也是同样。由于出口节点是一定含有有用操作,所以上述操作在向后查找的过程中,一定会停止。 ## AI 编译器中的死代码消除 AI 编译器通常是通过分析计算图,找到无用的计算节点或不可达的计算节点,然后消除这些节点。 -在计算图中,不可达节点是指从输入节点通过图中的有向边无法到达的节点。如下图,计算图中有 A,B,C 三个算子,假设三个算子都不是输入节点。不存在一条路径从输入节点到 B 节点,所以 B 节点是不可达节点,AI 编译器会删除该节点,并删除其到可达节点的边,即边 B ->C。 +在计算图中,不可达节点是指从输入节点通过图中的有向边无法到达的节点。如下图,计算图中有 A,B,C 三个算子,假设三个算子都不是输入节点。不存在一条路径从输入节点到 B 节点,所以 B 节点是不可达节点,AI 编译器会删除该节点,并删除其到可达节点的边,即边 $B \rightarrow C$。 ![不可达计算节点](images/09DCE02.png) @@ -139,7 +139,7 @@ AI 编译器通常是通过分析计算图,找到无用的计算节点或不 ## 死代码消除案例 -以 Golang 为例,简单了解一下传统编译器是死代码消除的一种实现。 +以 Golang 为例,简单了解一下AI编译器是死代码消除的一种实现。 1. 通过控制流分析,找到可达的基本块(ReachableBlocks 函数)。编译器通过深度优先搜索,从入口基本块开始遍历控制流图。将遍历到的所有基本块都标记为可达。 2. 从不可达的基本块中删除出边。遍历控制流图中的所有基本块,如果某个基本块 $b_n$ 未被标记为可达,则遍历 $b_n$ 的所有后继节点,如果后继节点是可达的,则删除 $b_n$ 到该节点的边。