乐趣区

Go编译器简介【译】

趁着元旦休假 + 春节,尝试把 2018 年期间让我受益的一些文章、问答,翻译一下。欢迎指正、讨论,希望对你也有所帮助。原文链接:https://github.com/golang/go/…
构成 Go 编译器的关键 package 都包含在 cmd/compile 目录。我们从逻辑上把编译器编译过程分成四个阶段,下文将简要介绍这四个阶段的 package 列表。谈到编译器,你可能听说过类似“前端”、“后端”这样的字眼。粗略地讲,我们也将编译器的四个阶段工作划分成了前两个阶段和后两个阶段,也就是前端和后端。还有一个词——”中端“,通常包含在第二个阶段中(译者注:有的编译器在前端和后端之间引入了一个代码优化阶段,称为 middle-end,中端。感兴趣的读者可以提前深入了解下一般编译器架构)。需要注意的是,go/ 目录的 package 和编译器无关,比如 go/types 等。由于最初的编译器是用 C 语言编写而成,go/ 中包含了一些用 Go 代码编写的工具,如 gofmt 和 vet。还有一点需要澄清,”gc“代表”Go compiler“,和我们常用来表示垃圾回收的 GC 没啥关系。
1. 词法分析和语法分析
cmd/compile/internal/syntax 目录包含了词法分析、语法分析和语法树构造部分。编译器第一阶段工作中,对每个源文件进行词法、语法分析,并生成 AST(抽象语法树)。每个语法树都精确表达了相应的源代码文件。树的节点对应着各个源文件的元素,例如表达式、声明语句。语法树还包含了代码位置信息,为错误报告和调试信息提供支持。
2. 类型检查和 AST 转换
cmd/compile/internal/gc 目录包含了编译器 AST 创建、类型检查、AST 转换部分。这个 package 中包含了一个从 C 继承的 AST 定义。这个 package 的第一要事就是把 syntax 包的语法树转换成编译器支持的 AST 来表示。这个步骤看起来略显多余,在将来的版本中可能会被重构。接下来要做的是类型检查。第一步做名字解析和类型推断,确定对象属于哪个标识符以及表达式的类型。类型检查还包括一些额外操作,例如判断”声明但未使用“的变量、确定函数是否会终止。某些节点类型的细化也会在这部分完成。例如从算术加节点中拆分出字符串加、无用代码消除、函数调用内联化和逃逸分析。
3. 通用 SSA
这里有官方给出的 SSA 背景介绍。cmd/compile/internal/gc(SSA 转换)cmd/compile/internal/ssa(SSA pass 和规则)这个阶段,AST 将转换为静态单赋值(SSA)形式。这是一种具有特定属性的低级中间表示法,更容易优化和生成机器码。转换期间还要处理内置函数。现代编译器经过多代进化已经很智能,会用大量优化代码替代内置函数,获得更高性能。在 AST 到 SSA 转换期间,为了方便复用,一些节点也降级为更简单的组件。例如,内置拷贝(译者猜:copy?)替换内存移动(译者猜:memmove?)并将 range 重写为 for。由于历史原因,其中一些在转换为 SSA 之前完成,未来计划全部移至这里。然后,一系列与机器无关的 pass 和规则被应用。这些不涉及任何单一计算机体系结构。这些环节包括消除无用代码、删除非必需的空值检查以及删除未使用的分支。通用重写规则主要涉及表达式,例如用常量替换、乘法和浮点运算的优化。
4. 生成机器代码
cmd/compile/internal/ssa(SSA 低级化和特定架构处理)cmd/internal/obj(机器码生成)机器依赖相关的阶段以比较“较低”的操作开始,这些操作将通用变量重写成其特定于机器的变体。例如,amd64 架构中,可以合并许多加载存储操作。要注意”低级“的操作运行所有机器特定的重写规则,它也应用了大量优化。一旦 SSA 被“低级”处理并且更具体地针对目标体系架构,就要运行最终代码优化的处理步骤了。其中包含了另一个无用代码消除的步骤。该步骤会将变量移动到更靠近它们被使用的位置、删除从未被读取的局部变量以及寄存器分配。此步骤完成的其他重要工作包括堆栈布局和指针分析。堆栈布局将堆栈偏移分配给局部变量,指针分析计算每个 GC 安全点上的堆栈指针是否仍然是活动的。
在 SSA 生成阶段结束时,Go 函数已转换为 sequence。这些 sequence 最终被转换成机器码。

退出移动版