想要深入了解 Go语言(GoLang)的编译过程,需要提前了解一下编译过程中涉及的一些术语和专业知识。
这些知识其实在我们的日常工作和学习中比较难用到,但是对于理解编译的过程和原理还是非常重要的。
这一小节会简单挑选几个常见并且重要的概念提前进行介绍,减少后面章节的理解压力。
抽象语法树
抽象语法树(AST),是源代码语法的结构的一种抽象表示,它用树状的方式表示编程语言的语法结构。
抽象语法树中的每一个节点都表示源代码中的一个元素,每一颗子树都表示一个语法元素,例如一个 if else 语句,我们可以从 2 * 3 + 7 这一表达式中解析出下图所示的抽象语法树。
简单表达式的抽象语法树
作为编译器常用的数据结构,抽象语法树抹去了源代码中不重要的一些字符 - 空格、分号或者括号等等。
编译器在执行完语法分析之后会输出一个抽象语法树,这个抽象语法树会辅助编译器进行语义分析,我们可以用它来确定语法正确的程序是否存在一些类型不匹配或不一致的问题。
静态单赋值
静态单赋值(Static Single Assigment, SSA)是中间代码的一个特性,如果一个中间代码具有静态单赋值的特性,那么每个变量就只会被赋值一次。
在实践中我们通常会用添加下标的方式实现每个变量只能被赋值一次的特性,这里以下面的代码举个例子:
x := 1
x := 2
y := x
根据分析,我们其实能够发现上述的代码其实并不需要第一个将 1 赋值给 x 的表达式,也就是 x := 1 这一表达式在上述的代码片段中是没有作用的。
x1 := 1
x2 := 2
y1 := x2
当我们使用具有 SSA 特性的中间代码时,就可以非常清晰地发现变量 y1 的值和 x1 是完全没有任何关系的,所以在机器码生成时其实就可以省略第一步,这样就能减少需要执行的指令来优化这一段代码。
在中间代码中使用 SSA 的特性能够为整个程序实现以下的优化:
1.常数传播(constant propagation)
2.值域传播(value range propagation)
3.稀疏有条件的常数传播(sparse conditional constant propagation)
4.消除无用的程式码(dead code elimination)
5.全域数值编号(global value numbering)
6.消除部分的冗余(partial redundancy elimination)
7.强度折减(strength reduction)
8.寄存器分配(register allocation)
因为 SSA 的主要作用是对代码进行优化,所以它是编译器后端3的一部分;当然代码编译领域除了 SSA 还有很多中间代码的优化方法,编译器生成代码的优化也是一个非常古老并且复杂的领域,这里就不会展开介绍了。
指令集
最后要介绍的一个预备知识就是指令集了,很多开发者都会遇到在生产环境运行的结果和本地不同的问题,导致这种情况的原因其实非常复杂,不同机器使用不同的指令也是可能的原因之一。
我们大多数开发者都会使用 x86_64 的 Macbook 作为工作上主要使用的硬件,在命令行中输入 uname -m 就能够获得当前机器上硬件的信息:
$ uname -m
x86_64
x86 是目前比较常见的指令集,除了 x86 之外,还有很多其他的指令集,不同的处理器使用了不同的架构和机器语言,所以很多编程语言为了在不同的机器上运行需要将源代码根据架构翻译成不同的机器代码。
复杂指令集计算机(CISC)和精简指令集计算机(RISC)是目前的两种 CPU 区别,它们的在设计理念上会有一些不同,从名字我们就能看出来这两种不同的设计有什么区别:
· 复杂指令集通过增加指令的数量减少需要执行的指令数;
· 精简指令集能使用更少的指令完成目标的计算任务;
早期的 CPU 为了减少机器语言指令的数量使用复杂指令集完成计算任务,这两者其实并没有绝对的好坏,它们只是在一些设计上的选择不同以达到不同的目的,我们会在后面的机器码生成一节中详细介绍指令集架构,不过各位读者也可以主动了解相关的内容。
相关阅读 >>
更多相关阅读请进入《Go》频道 >>

Go语言101
一个与时俱进的Go编程知识库。