# 2.2 Go 程序编译流程
## 2.2.1 第一阶段:词法和语法分析
* `cmd/compile/internal/syntax`(词法分析器,解析器,语法树)
在编译的第一阶段,源代码被 token 化(词法分析),解析(语法分析),并为每个源构造语法树文件。每个语法树都是相应源文件的精确表示对应于源的各种元素的节点,如表达式,声明和陈述。语法树还包括位置信息用于错误报告和调试信息的创建。
~~~
main -> gc.Main -> amd64.Init -> amd64.LinkArch.Init
-> typecheck -> typecheck -> saveerrors -> typecheckslice
-> checkreturn -> checkMapKeys -> capturevars ->
typecheckinl -> inlcalls -> escapes ->
newNowritebarrierrecChecker -> transformclosure
~~~
## 2.2.2 第二阶段:语义分析
* `cmd/compile/internal/gc`(类型检查,AST变换)
对 AST 进行类型检查。第一步是名称解析和类型推断,它们确定哪个对象属于哪个标识符,以及每个表达式具有的类型。类型检查包括某些额外的检查,例如 “声明和未使用” 以及确定函数是否终止。
在 AST 上也进行了某些转换。一些节点基于类型信息被细化,例如从算术加法节点类型分割的字符串添加。其他一些例子是死代码消除,函数调用内联和转义分析。
语义分析的过程中包含几个重要的操作:逃逸分析、变量捕获、函数内联、闭包处理。
## 2.2.3 第三阶段:SSA 生成
* `cmd/compile/internal/gc`(转换为SSA)
* `cmd/compile/internal/ssa`(SSA 传递与规则)
在此阶段,AST将转换为静态单一分配(SSA)形式,这是一种具有特定属性的低级中间表示,可以更轻松地实现优化并最终从中生成机器代码。
在此转换期间,将应用函数内在函数。 这些是特殊功能,编译器已经教导它们根据具体情况用大量优化的代码替换。
在AST到SSA转换期间,某些节点也被降级为更简单的组件,因此编译器的其余部分可以使用它们。 例如,内置复制替换为内存移动,并且范围循环被重写为for循环。 其中一些目前发生在转换为SSA之前,由于历史原因,但长期计划是将所有这些都移到这里。
然后,应用一系列与机器无关的传递和规则。 这些不涉及任何单个计算机体系结构,因此可以在所有`GOARCH` 变体上运行。
这些通用过程的一些示例包括消除死代码,删除不需要的零检查以及删除未使用的分支。通用重写规则主要涉及表达式,例如用常量值替换某些表达式,以及优化乘法和浮点运算。
~~~
initssaconfig -> peekitabs -> funccompile ->
finit -> compileFunctions -> compileSSA -> buildssa -> genssa ->
-> typecheck -> checkMapKeys -> dumpdata -> dumpobj
~~~
## 2.2.4 第四阶段:机器码生成
* `cmd/compile/internal/ssa`(底层SSA和架构特定的传递)
* `cmd/internal/obj`(生成机器码)
编译器的机器相关阶段以“底层”传递开始,该传递将通用值重写为其机器特定的变体。例如,在 amd64 存储器操作数上是可能的,因此可以组合许多加载存储操作。
请注意,较低的通道运行所有特定于机器的重写规则,因此它当前也应用了大量优化。
一旦SSA“降低”并且更加特定于目标体系结构,就会运行最终的代码优化过程。这包括另一个死代码消除传递,移动值更接近它们的使用,删除从未读取的局部变量,以及寄存器分配。
作为此步骤的一部分完成的其他重要工作包括堆栈框架布局,它将堆栈偏移分配给局部变量,以及指针活动分析,它计算每个 GC 安全点上的堆栈指针。
在SSA生成阶段结束时,Go 函数已转换为一系列`obj.Prog` 指令。它们会被传递给装载器(`cmd/internal/obj`),将它们转换为机器代码并写出最终的目标文件。目标文件还将包含反射数据,导出数据和调试信息。
- 第一部分 :基础篇
- 第1章 Go语言的前世今生
- 1.2 Go语言综述
- 1.3 顺序进程通讯
- 1.4 Plan9汇编语言
- 第2章 程序生命周期
- 2.1 从go命令谈起
- 2.2 Go程序编译流程
- 2.3 Go 程序启动引导
- 2.4 主Goroutine的生与死
- 第3 章 语言核心
- 3.1 数组.切片与字符串
- 3.2 散列表
- 3.3 函数调用
- 3.4 延迟语句
- 3.5 恐慌与恢复内建函数
- 3.6 通信原语
- 3.7 接口
- 3.8 运行时类型系统
- 3.9 类型别名
- 3.10 进一步阅读的参考文献
- 第4章 错误
- 4.1 问题的演化
- 4.2 错误值检查
- 4.3 错误格式与上下文
- 4.4 错误语义
- 4.5 错误处理的未来
- 4.6 进一步阅读的参考文献
- 第5章 同步模式
- 5.1 共享内存式同步模式
- 5.2 互斥锁
- 5.3 原子操作
- 5.4 条件变量
- 5.5 同步组
- 5.6 缓存池
- 5.7 并发安全散列表
- 5.8 上下文
- 5.9 内存一致模型
- 5.10 进一步阅读的文献参考
- 第二部分 运行时篇
- 第6章 并发调度
- 6.1 随机调度的基本概念
- 6.2 工作窃取式调度
- 6.3 MPG模型与并发调度单
- 6.4 调度循环
- 6.5 线程管理
- 6.6 信号处理机制
- 6.7 执行栈管理
- 6.8 协作与抢占
- 6.9 系统监控
- 6.10 网络轮询器
- 6.11 计时器
- 6.12 非均匀访存下的调度模型
- 6.13 进一步阅读的参考文献
- 第7章 内存分配
- 7.1 设计原则
- 7.2 组件
- 7.3 初始化
- 7.4 大对象分配
- 7.5 小对象分配
- 7.6 微对象分配
- 7.7 页分配器
- 7.8 内存统计
- 第8章 垃圾回收
- 8.1 垃圾回收的基本想法
- 8.2 写屏幕技术
- 8.3 调步模型与强弱触发边界
- 8.4 扫描标记与标记辅助
- 8.5 免清扫式位图技术
- 8.6 前进保障与终止检测
- 8.7 安全点分析
- 8.8 分代假设与代际回收
- 8.9 请求假设与实务制导回收
- 8.10 终结器
- 8.11 过去,现在与未来
- 8.12 垃圾回收统一理论
- 8.13 进一步阅读的参考文献
- 第三部分 工具链篇
- 第9章 代码分析
- 9.1 死锁检测
- 9.2 竞争检测
- 9.3 性能追踪
- 9.4 代码测试
- 9.5 基准测试
- 9.6 运行时统计量
- 9.7 语言服务协议
- 第10章 依赖管理
- 10.1 依赖管理的难点
- 10.2 语义化版本管理
- 10.3 最小版本选择算法
- 10.4 Vgo 与dep之争
- 第12章 泛型
- 12.1 泛型设计的演进
- 12.2 基于合约的泛型
- 12.3 类型检查技术
- 12.4 泛型的未来
- 12.5 进一步阅读的的参考文献
- 第13章 编译技术
- 13.1 词法与文法
- 13.2 中间表示
- 13.3 优化器
- 13.4 指针检查器
- 13.5 逃逸分析
- 13.6 自举
- 13.7 链接器
- 13.8 汇编器
- 13.9 调用规约
- 13.10 cgo与系统调用
- 结束语: Go去向何方?