Go 编译器概述
本文基于 Go 1.13
Go 编译器是 Go 生态系统中的一个重要工具,因为它是将程序构建为可执行二进制文件的基本步骤之一。编译器的历程是漫长的,它先用 C 语言编写,迁移到 Go,许多优化和清理将在未来继续发生,让我们来看看它的高级操作。
阶段(phases)
Go 编译器由四个阶段组成,可以分为两类:
- 前端(frontend):这个阶段从源代码进行分析,并生成一个抽象的源代码语法结构,称为 AST
- 后端(backend):第二阶段将把源代码的表示转换为机器码,并进行一些优化。
为了更好理解每个阶段,我们看个简单的程序:
package main
func main() {
a := 1
b := 2
if true {
add(a, b)
}
}
func add(a, b int) {
println(a + b)
}
解析
第一阶段非常简单,在 文档 中有很好的解释:
在编译的第一阶段,对源代码进行标记(词法分析)、解析(语法分析),并为每个源文件构建语法树。
lexer 是第一个运行用来标记源代码的包。下面是上边例子的 标记化 输出:
一旦被标记化,代码将被解析、构建代码树。
AST(抽象语法树) 转换
可以通过 go tool compile
命令和标志 -w
展示 抽象语法树 的转换:
此阶段还将包括内联等优化。在我们的示例中,由于我们没有看到 CALLFUNC
该方法的任何 add
指令,该方法 add
已经内联。让我们使用禁用内联的标志 -l
再次运行。
AST 生成后,它允许编译器使用 SSA 表示转到较低级别的中间表示。
SSA(静态单赋值)的生成
静态单赋值 阶段进行优化:消除死代码,删除不使用的分支,替换一些常量表达式等等。
使用 GOSSAFUNC=main Go tool compile main.go && open ssa.html
命令,生成 HTML 文档的命令将在 SSA 包中完成所有不同的过程,因此可以转储 SSA 代码:
生成的 SSA 位于 “start” 选项卡中:
在这里,高亮显示变量 a
和 b
以及 if
条件表达式,将向我们展示这些行是怎么变化的。这些代码也向我们描述了编译器如何管理 println
函数,该函数被分解为 4 个步骤:printlock、printint、printnl、printunlock。编译器会自动为我们添加一个锁,并根据参数的类型,调用相关的方法来正确输出。
在我们的示例中,由于编译时已知 a
和 b
,所以编译器可以计算最终结果并将变量标记为不必要的。通过 opt
优化这部分:
在这里,v11
已经被添加的 v4
和 v5
所替代,这两个 v4
和 v5
被标记为死代码。然后通过 opt deadcode
将删除这些代码。
对于 if
条件,opt
阶段将常量 true
标记为死代码,然后删除:
然后,通过将不必要的块和条件标记为无效,另一次传递将简化控制流。这些块稍后将被另一个专用于死代码的阶段删除
完成所有过程之后,Go 编译器现在将生成一个中间汇编代码
下一阶段将把机器码生成到二进制文件中。
生成机器码
编译器的最后一步是生成目标(object)文件,在我们的例子中生成 main.c
。从这个文件中,现在可以使用 objdumptool
对其进行反编译。下面是一个很好的图表,由 Grant Seltzer Richman 创建:
您可以在“Dissecting Go Binaries”中找到有关对象文件和二进制文件的更多信息。
生成目标文件后,现在可以使用 go tool link
将其直接传递给链接器,二进制文件将最终就绪。
via: https://medium.com/a-journey-with-go/go-overview-of-the-compiler-4e5a153ca889
作者:Vincent Blanchon 译者:TomatoAres 校对:polaris1119
- 原文作者:战神西红柿
- 原文链接:https://tomatoares.github.io/posts/go/translated/go-compiler-overview/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。