本文基于Go 1.13。
Go编译器是Go生态系统中的一个重要工具,因为它是将程序构建为可执行二进制文件的基本步骤之一。编译器的历程是漫长的,它已经用C语言编写,转移到Go,许多优化和清理将在未来继续发生。让我们发现它的高水平运作。
Phases
Go编译器由四个阶段组成,可分为两类:
- 前端。此阶段从源代码运行分析,并生成源代码的抽象语法结构,称为AST。
- 后端。第二阶段将把源代码的表示转换为机器代码,以及几个优化。
为了更好地理解每个阶段,让我们使用一个简单的程序:
package mainfunc main() { a := 1 b := 2 if true { add(a, b) }}func add(a, b int) { println(a + b)}
解析
第一阶段非常简单,并在文档中做了很好的解释:
在编译的第一阶段,源代码被标记化(词法分析),解析(语法分析),并且为每个源文件构造语法树。
词法分析器将是第一个为了标记源代码而运行的包。这是前一个示例标记化的输出:
去源代码标记化
一旦被标记化,将被解析并用于构建语法树。
AST转换
由于带有标志的命令,可以显示对抽象语法树的转换:go tool compile``-W
生成AST的样本
此阶段还将包括内联等优化。在我们的示例中,该方法add可以已经内联,因为我们没有看到CALLFUNC该方法的任何指令add。让我们使用禁用内联的标志-l运行again命令:
AST生成后,它允许编译器使用SSA表示转到较低级别的中间表示。
SSA
在静态单赋值形式是阶段,其中优化会发生:死代码消除,删除不使用的分支,具有恒定值等替换一些表达式
由于GOSSAFUNC=main go tool compile main.go && open ssa.html生成HTML文档的命令将在SSA包中完成所有不同的传递,因此可以转储SSA代码:
生成的SSA位于“开始”选项卡中:
SSA代码
这里的变量a和b突出显示以及if条件将允许我们稍后查看这些行是如何更改的。该代码也向我们展示了编译器如何管理println是在4个步骤分解函数:printlock,printint,printnl,printunlock。编译器会自动为我们添加一个锁,并根据参数的类型调用相关方法来正确打印它。
在我们的示例中,由于编译时已知a并且b已知,编译器可以计算最终结果并将变量标记为不再需要。通行证opt将优化此部分:
v11这里已经被添加的结果替换,v4并且v5已被标记为死代码。该传递opt deadcode将删除该代码:
关于if条件,opt阶段将常量标记true为死代码,然后将被删除:
常量布尔值被删除
然后,另一个过程将通过将不必要的块和条件标记为无效来简化控制流程。这些块稍后将被另一个专用于死代码的传递删除:
不必要的控制流程被删除
完成所有传递后,Go编译器现在将生成一个中间汇编代码:
go asm代码
下一阶段将生成机器代码到二进制文件中。
机器代码生成
main.o在我们的示例中,编译器的最后一步是生成目标文件。从该文件中,现在可以使用objdump执行相反过程的工具对其进行反汇编。这是Grant Seltzer Richman创建的一个很好的图表:
go 工具编译
go 工具objdump
您可以在“ Dissecting Go Binaries ”中找到有关目标文件和二进制文件的更多信息。
生成目标文件后,现在可以使用该命令go tool link将其直接传递给链接器,您的二进制文件最终将准备就绪。
翻译自:https://medium.com/a-journey-with-go/go-overview-of-the-compiler-4e5a153ca889