浏览代码

feat: 完善代码生成流程

nicetry12138 1 年之前
父节点
当前提交
e900bf4697

二进制
Build-Project/程序员的自我修养/Image/003.png


二进制
Build-Project/程序员的自我修养/Image/004.png


+ 124 - 0
Build-Project/程序员的自我修养/README.md

@@ -99,6 +99,8 @@ int main() {
 | Target Code | 目标代码 |
 | Code Optimizer | 目标代码优化器 |
 
+### 词法分析
+
 **扫描器**:简单地进行**词法分析**,运用类似有**限状态机**的算法可以将源代码的字符序序列分割成一系列的**记号**(`Token`)
 
 通过 `Scanner` 扫描器的词法分析产生的记号 `Token` 一般分为一下几类:关键字、标识符、字面量(数字、字符串等)和特殊符号(加号、减号等)
@@ -130,8 +132,12 @@ array[index] = (index + 4) * (2 + 6);
 | 6 | 数字 |
 | `)` | 右圆括号 |
 
+### 语法分析
+
 语法分析器(`Grammar Parser`) 对扫描器(`Scanner`) 产生的记号进行语法分析,从而产生语法树(`Syntax Tree`),整个过程采用上下文无关语法(`Context-Free Grammar`)
 
+对于 `int x = (1 + 2;` 这种错误就是在语法分析阶段被检查出来
+
 通过**语法分析器**生成的**语法树**就是以**表达式**为节点的树
 
 ![](Image/002.png)
@@ -142,3 +148,121 @@ array[index] = (index + 4) * (2 + 6);
 
 > 对于不同的语言,编译器的开发者只需要改变语法规则,而无需为每个编译器编写一个语法分析器
 
+### 语义分析
+
+语法分析仅仅完成了对表达式的语法层面的分析,但是并不了解这个语句是否真正有意义
+
+```cpp
+int* p1 = NULL;
+int* p2 = NULL;
+int p3 = p1 * p2;
+```
+
+比如上述代码在语法分析阶段是通过的,但是在语义分析阶段不能通过
+
+编译器能分析的语义是静态语义(`Static Semantic`),也就是编译器可以确定的语义。对应的还有动态语义(`dynamic Semantic`),只有在运行期才能确定的语义
+
+- 静态语义:类型检查、变量声明检查、控制流检查等。例如,编译器会检查变量是否在使用前声明,类型转换是否合法等。这些检查在编译时就能确定,因此称为静态语义
+- 动态语义:数组越界、除零错误等。这些错误只有在程序实际运行时才能被检测到,因此称为动态语义
+
+经过语义分析之后,语法树的表达式都会被标识上类型,如果有些类型需要做隐式转换,语义分析程序会在语法树中插入相应的转换节点
+
+![](Image/003.png)
+
+### 中间语言生成
+
+现代编译器有很多层次的优化,往往在**源代码级别**会有一个优化过程
+
+这里定义的**源码级优化器**(`Source Code Optimizer`)在不同的编译器中可能会有不同的定义或者一些其他的差异
+
+比如之前的例子 `array[index] = (index + 4) * (2 + 6);` 中 **(2 + 6)** 的值在编译期就可以被确定
+
+![](Image/004.png)
+
+> 上述的语法树经过优化了
+
+但是一般不会直接优化在语法树上做优化,这比较困难,**源代码优化器** 往往将整个语法树转换成**中间代码**(`Intermediate Code`),它是语法树的顺序表示,非常接目标代码
+
+> 中间代码一般与目标机器和运行时环境无关,比如不包含数据的尺寸、变量地址和寄存器的名字等
+
+中间代码有很多类型,在不同编译器中有不同的形式,比较常见的有:**三地址码**(`Three-Address Code` Or `TAC`)和 **P-代码**(`P-Code`)
+
+**三地址码** 是常见的中间表示形式,每条三地址码指令最多包含三个地址:两个操作数和一个结果
+
+1. 四元组表示,每条三地址码可以表示为一个四元组(`4-tuple`):运算符、操作数1、操作数2、结果
+2. 简单命令
+3. 线性表示,便于编译器进行顺序处理和优化
+
+```cpp
+array[index] = (index + 4) * (2 + 6);
+```
+
+上述代码以 **三地址码** 为例,会生成如下的中间代码
+
+```cpp
+t1 = 2 + 6
+t2 = index + 4
+t3 = t2 * t1
+array[index] = t3
+```
+
+**三地址码**的优化过程主要包括消除公共子表达式、常量折叠、赋值传播等技术
+
+所以**优化程序**在三地址码的基础上进行优化时
+
+- 常量折叠:会将 `2 + 6` 计算出来得到 `t1 = 8`,然后将后面代码中的 t1 替换成数字 3
+- 复制传播:省去一个临时变量 t3,因为 t2 可以重复利用
+
+最后得到优化后的三地址码如下
+
+```cpp
+t2 = index + 4
+t2 = t2 * 8
+array[index] = t2
+```
+
+至于什么是消除公共子表达式,例子如下
+
+```cpp
+// 原始三地址码
+t1 = b + c
+a = t1
+t2 = b + c
+d = t2
+t3 = a * d
+e = t3
+
+// 因为 t1 和 t2 表达式相同
+t1 = b + c
+a = t1
+d = t1  // 直接使用 t1 而不是重新计算 b + c
+t3 = a * d
+e = t3
+```
+
+### 目标代码生成与优化
+
+**源代码级优化器**产生的中间代码标志着下面的过程都属于**编译器后端**
+
+编译器后端负责将中间代码转换为目标机器代码,并进行各种优化以提高代码的执行效率。主要包括:**代码生成器**和**目标代码优化器**
+
+代码生成器将中间代码转换成目标机器代码,这个过程十分依赖目标机器,因为不同的机器有着不同的字长、寄存器、整数数据类型和浮点数数据类型等
+
+```x86asm
+movl index, %ecx                ; 赋值 index 给 ecx
+addl $4, %ecx                   ; ecx = ecx + 4
+mull $8, %ecx                   ; ecx = ecx * 8
+movl index, %eax                ; 赋值 index 给 eax
+movl %ecx, array(, eax, 4)      ; array[index] = ecx
+```
+
+最后目标代码优化器对上述的目标代码进行优化,比如选择合适的寻址方式、使用位移来代替乘法运算、删除多余的指令等
+
+```x86asm
+movl index, %edx
+leal 32(, %edx, 8), %eax
+movl %eax, array(, %edx, 4)
+```
+
+> 乘法由一条相对复杂的 **基址比例变址寻址**(`Base Index Scale Addressing`) 的 lea 指令完成
+