目标:把源代码变成目标代码前端
1.若是源代码在操做系统上运行:目标代码就是“汇编代码”。再经过汇编和连接的过程造成可执行文件,而后经过加载器加载到操做系统执行。算法
2.若是源代码在虚拟机(解释器)上运行:目标代码就是“解释器能够理解的中间形式的代码”,好比字节码(中间代码)IR、AST语法树。数据结构
编译过程能够分为这几个阶段,每一个阶段作了必定的任务,层级的让下一个阶段进行。
架构
编译器读入源代码,通过词法分析器识别出Token,把字符串转换成一个个Token。
Token的类型包括:关键字、标识符、字面量、操做符、界符等函数
好比下面的C语言代码源文件,通过词法分析器识别出的token有:int、foo、a、b、=、+、return、(){}等token工具
int foo(int a){ int b = a + 3; return b; }
为何要这样作呢,把代码里的单词进行分类,编译器后面的阶段不就更好处理理解代码了嘛!性能
每个程序代码,实际上能够经过树这种结构表现出其语法规则。优化
语法分析阶段把Token串,转换成一个体现语法规则的、树状数据结构,即抽象语法树AST。
AST树反映了程序的语法结构。操作系统
好比下面对应的一段C语言代码,对应的AST抽象语法树以下所示:翻译
int foo(int a){ int b = a + 3; return b; }
AST抽象语法树
AST树长成什么样,由语法的结构有关。
好比 上面C语言代码中对函数的语法定义以下:语法分析器就按照语法定义进行解析,就是从上到下匹配的过程。
也就是先匹配function的规则,匹配函数类型type、函数名name、函数参数parameters、函数体
当匹配函数参数时,就去匹配parameters的规则
当匹配函数体时,函数体由一个个语句组成,就去匹配各个语句stmt的规则。
function := type name parameters functionBody parameters:= parameter* functionBody:= stmt returnStatement
生成 AST 之后,程序的语法结构就很清晰了,但这棵树到底表明了什么意思,咱们目前仍然不能彻底肯定,要在语义分析阶段肯定。
为何要把程序转换成AST这么一颗树,由于编译器不像人能直接理解语句的含义,AST树更有结构性,后续阶段能够针对这颗树作各类分析!
语义分析阶段的任务:理解语义,语句要作什么。
好比+号要执行加法、=号要执行赋值、for结构要去实现循环、if结构实现判断。
因此语义阶段要作的内容有:上下文分析(包括引用消解、类型分析与检查等)
引用消解:找到变量所在的做用域,一个变量做用范围属于全局仍是局部。
类型识别:好比执行a+3,须要识别出变量a的类型,由于浮点数和整型执行不同,要执行不一样的运算方式。
类型检查:好比int b = a + 3,是否能够进行定义赋值。等号右边的表达式必须返回一个整型的数据、或则可以自动转换成整型的数据,才可以对类型为整型的变量b进行复制。
好比以前的一段C语言代码,通过语义分析后得到的信息(引用消解信息、类型信息),能够在AST上进行标注,造成下面的“带有标注的语法树”,让编译器更好的理解程序的语义。
也会将这些上下文信息存入“符号表”结构中,便于各阶段查询上下文信息。
符号表是有层次的结构:咱们只须要逐级向上查找就能找到变量、函数等的信息(做用域、类型等)
接下来就能够 解释执行:实现一门解释型的语言
Tip:编译型语言须要生成目标代码,而解释性语言只须要解释器去执行语义就能够了。
实现AST的解释器:在语法分析后有了程序的抽象语法树,在语义分析后有了“带有标注的AST”和符号表后,就能够深度优先遍历AST,而且一边遍历一边执行结点的语义规则。整个遍历的过程就是执行代码的过程。
举一个解释执行的例子,好比执行下面的语义:
在编译前端完成后(编译器已经理解了词法和语义),编译器能够直接解释执行、或则直接生成目标代码。对于不一样架构的CPU,还须要生成不一样的汇编代码,若是对每一种汇编代码作优化就很繁琐了。因此咱们须要增长一个环节:生成中间代码IR,统一优化后中间代码,再去将中间代码生成目标代码。
中间代码IR的两个用途:解释执行 、代码优化
解释执行:解释型语言,好比Python和Java,生成IR后就能直接执行了,也就是前面举出的例子。
优化代码:好比LLVM等工具;在生成代码后须要作大量的优化工做,而不少优化工做不必使用汇编代码来作(由于不一样CPU体系的汇编语言不一样),而能够基于IR用统一的算法来完成,下降编译器适配不一样CPU的复杂性。
一种方案:基于基本块做代码优化
分类:本地优化、全局优化、过程间优化
本地优化:可用表达式分析、活跃性分析
全局优化:基于控制流图CFG做优化。
控制流图CFG :是一种有向图,它体现了基本块以前的指令流转关系,若是从BLOCK1的最后一条指令是跳转到 BLOCK2, 就连一条边,若是经过分析 CFG,发现某个变量在其余地方没有被使用,就能够把这个变量所在代码行删除。
过程间优化:跨越函数的优化,多个函数间做优化
优化案例:
代数优化:
好比删除“x:=x+0 ”,乘法优化掉“x:=x乘以0” 能够简化成“x:=0”,乘法优化成移位运算:“x:=x*8”能够优化成“x:=x<<3”。
常数折叠:
对常数的运算能够在编译时计算,好比 “x:= 20 乘以 3 ”能够优化成“x:=60”
删除公共子表达式:做“可用表达式分析”
x := a + b y := a + b //优化成y := x
拷贝传播:做“可用表达式分析”
x := a + b y := x z := 2 * y //优化成z:= 2 * x
常数传播:
x := 20 y := 10 z := x + y//优化成z := 30
死代码删除:做变量的“活跃性分析”
活跃性分析(优化删除死代码,没用到的变量) 数据流分析:使用“半格理论”解决多路径的V值计算集合问题,不在代码下面集合的变量就是死代码。
目标代码生成,也就是生成虚拟机执行的字节码,或则操做系统执行的汇编代码
代码生成的过程,其实很简单,就是将中间代码IR逐个翻译成想要的汇编的代码
那么目标代码生成阶段的任务就有: