Go program 编译过程
package main
import "fmt"
func main() {
fmt.Println("Hello Go")
}
编译前端
- 词法分析
- 将源代码翻译成 Token(最小语义结构)
- 句法分析
- Token 序列经过处理,变成语法树 AST
- 语义分析
- 类型检查
- 类型推断
- 函数调用内联
- 逃逸分析
编译后端
- 中间代码生成(SSA)
- 处理不同平台的差异,生成中间代码
- 代码优化
- 机器码生成
- 生成 Plan9 汇编代码
- 链接
- 将各个包(.a 文件)进行链接,包括 runtime
查看从代码到 SSA 中间码的过程
# windows
$env:GOSSAFUNC="main"
# linux
export GOSSAFUNC="main"
执行 go build main.go
,得到 ssa.html 文件。
从 sourses -> ast -> … -> genssa
查看 Plan9 汇编代码
go build -gcflags -S main.go
Go program 的入口
tips: 第一行代码并非为用户自定义的 main 方法
- 首先进入
%GOROOT%\src\runtime
下的对应的rt0_xxx.s
汇编文件,如在 windows 系统下,进入rt0_windows_amd64.s
,执行JMP _rt0_amd64(SB)
:
- 调用
_rt0_amd64(SB)
方法,该方法位于%GOROOT%\src\runtime\asm_amd64.s
:
- 该方法将调用命令参数 argc,argc 放入寄存器,随后调用
runtime·rt0_go(SB)
:
- 读取命令行参数。复制参数数量 argc 和参数值 argv 到栈上,参数可能有也可能没有,取决于启动程序时参数是否输入。
- 初始化 g0 执行栈。启动 g0 协程,g0 是 Go 程序的第一个协程,且不归调度器管理,是为了调度协程而产生的协程。
- 经过一系列判断后,开始运行时检测
runtime.check
,该检测方法位于%GOROOT%\src\runtime\runtime1.go
。主要检测包括:
- 检查各种类型的长度
- 检查指针操作
- 检查结构体字段的偏移量
- 检查 atomic 原子操作
- 检查 CAS 操作
- 检查栈大小是否为 2 的幂次
- 初始化 runtime.args。对命令行中的参数进行处理,拷贝参数到 go 代码,参数数量赋值给 argc int32,参数值赋值给 argv **byte。
- 执行
runtime·osinit(SB)
判断 cpu 核数,用于后续调度器的初始化runtime·schedinit(SB)
:- 全局占空间内存分配
- 加载命令行参数到 os.Args
- 堆内存空间的初始化
- 加载操作系统环境变量
- 初始化当前系统线程
- 垃圾回收器的参数初始化
- 算法初始化
- 设置 process 数量
- 取 runtime 主函数地址
MOVQ $runtime·mainPC(SB), AX
,创建启动主协程runtime·newproc(SB)
,初始化调度器 Mruntime·mstart(SB)
,调度主协程用于运行 runtime.main,runtime 的 main 方法位于%GOROOT%\src\runtime\proc.go
。 - 主协程执行主函数。执行 runtime 中的 init 方法,启动垃圾回收器,执行用户包依赖的 init 方法,最后执行用户自定义的主函数 main.main():