本文基于Go 1.13。*
Go编译器是Go生态系统中的一个重要工具,因为它是将程序构建为可执行二进制文件的基本步骤之一。编译器的历程是漫长的,它已经用C语言编写,转移到Go,许多优化和清理将在未来继续发生。让我们发现它的高水平运作。
Phases
Go编译器由四个阶段组成,可分为两类:
- 前端。此阶段从源代码运行分析,并生成源代码的抽象语法结构,称为AST。
- 后端。第二阶段将把源代码的表示转换为机器代码,以及几个优化。
为了更好地理解每个阶段,让我们使用一个简单的程序:
package main
func 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