0%

golang 编译

[TOC]

概述

阶段

编译分为编译器的前端和后端,编译器的前端一般承担着词法分析、语法分析、类型检查和中间代码生成几部分工作,而编译器后端主要负责目标代码的生成和优化,也就是将中间代码翻译成目标机器能够运行的二进制机器码。

  • lexical analysis - 词法分析
  • syntax analysis - 语法分析
  • semantic analysis - 语义分析
  • Intermediate Code generation - 中间代码生成
  • code optimization - 代码优化
  • machine code generation - 类型检查

Go 的编译器在逻辑上可以被分成四个阶段:词法与语法分析、类型检查和 AST 转换、通用 SSA 生成和最后的机器代码生成。

词法和语法分析 -> AST

所有的编译过程其实都是从解析代码的源文件开始的,词法分析的作用就是解析源代码文件,它将文件中的字符串序列转换成 Token 序列,方便后面的处理和解析,我们一般会把执行词法分析的程序称为词法解析器(lexer)。

而语法分析的输入是词法分析器输出的 Token 序列,语法分析器会按照顺序解析 Token 序列,该过程会将词法分析生成的 Token 按照编程语言定义好的文法(Grammar)自下而上或者自上而下的规约,每一个 Go 的源代码文件最终会被归纳成一个 SourceFile 结构。语法分析会把 Token 序列转换成有意义的结构体,即语法树。Token 到上述抽象语法树(AST)的转换过程会用到语法解析器,每一个 AST 都对应着一个单独的 Go 语言文件,这个抽象语法树中包括当前文件属于的包名、定义的常量、结构体和函数等。

类型检查

当拿到一组文件的抽象语法树之后,Go 语言的编译器会对语法树中定义和使用的类型进行检查,类型检查会按照以下的顺序分别验证和处理不同类型的节点:

  • 常量、类型和函数名及类型;
  • 变量的赋值和初始化;
  • 函数和闭包的主体;
  • 哈希键值对的类型;
  • 导入函数体;
  • 外部的声明;

类型检查阶段不止会对节点的类型进行验证,还会展开和改写一些内建的函数,例如 make 关键字在这个阶段会根据子树的结构被替换成 runtime.makeslice 或者 runtime.makechan 等函数。

中间代码生成

在类型检查之后,编译器会通过 cmd/compile/internal/gc.compileFunctions 编译整个 Go 语言项目中的全部函数,这些函数会在一个编译队列中等待几个 Goroutine 的消费,并发执行的 Goroutine 会将所有函数对应的抽象语法树转换成中间代码。

机器码生成 -> machine code

Go 语言源代码的 src/cmd/compile/internal 目录中包含了很多机器码生成相关的包,不同类型的 CPU 分别使用了不同的包生成机器码,其中包括 amd64、arm、arm64、mips、mips64、ppc64、s390x、x86 和 wasm,其中比较有趣的就是 WebAssembly(Wasm)7了。

作为一种在栈虚拟机上使用的二进制指令格式,它的设计的主要目标就是在 Web 浏览器上提供一种具有高可移植性的目标语言。Go 语言的编译器既然能够生成 Wasm 格式的指令,那么就能够运行在常见的主流浏览器中。

golang build

编译和链接

  • 编译:编译阶段逻辑上其实可以细分为预处理编译汇编三个阶段。整个编译阶段就是通过词法分析,语法分析和语义分析,把文本代码翻译成可重定位的目标文件(.o文件)的过程, 如上图。其中,编译优化也发生在这个阶段。
    • 预处理: 例如: 解析依赖库。
    • 编译:将预处理后的代码,翻译成汇编代码(.s文件)。
    • 汇编:将生成的汇编代码,翻译成可重定位的目标文件(.o文件,relocatable object file)。注意,目标文件纯粹就是字节块的集合。
  • 链接:链接阶段主要是通过符号解析重定位把编译阶段生成的.o文件,链接生成可执行目标文件。
    • 符号解析:目标文件定义和引用符号。符号解析的目的是将每个符号引用和一个符号定义联系起来。
    • 重定位:编译阶段生成的代码和数据节都是从地址零开始的,链接器通过把每个符号定义与一个存储器位置联系起来,然后修改所有对这些符号的引用,使得它们指向这个存储器位置,从而重定向这些节(section)。进而生成可执行目标文件,如下图。

golang编译链接的过程

通常情况下,在Linux系统中我们敲入 go build hello.go就会生成一个文件名为hello的ELF(可链接可执行)目标文件,通常这就是我们所期望的行为。

执行命令显示编译链接的过程: go build -v -x -work -o hello hello.go

  • -v: 打印所编译的包的名字
  • -x: 打印编译期间所执行的命令
  • -work: 打印编译期间用于存放中间文件的临时目录,并且编译结束时不删除

预备知识

抽象语法树(AST)

抽象语法树(Abstract Syntax Tree、AST),是源代码语法的结构的一种抽象表示,它用树状的方式表示编程语言的语法结构1。抽象语法树中的每一个节点都表示源代码中的一个元素,每一棵子树都表示一个语法元素.

静态单赋值

静态单赋值(Static Single Assignment、SSA)是中间代码的特性,如果中间代码具有静态单赋值的特性,那么每个变量就只会被赋值一次2。

指令集

指令集是存储于CPU内部,用来引导CPU进行加减运算和控制计算机操作系统的一系列指令集合!

可以这样说指令集是软件与CPU之间的一个接口而CPU就是接口的实列化。

其实指令集就是一组汇编指令的集合,不同的CPU使用的指令集不同。

指令集就是电路,当电流流向不同的方向,根据路线到不同的电子元器件时,这就是指令集,指令集不存在任何一个器件里,cpu在设计时上面的线路构成了这些指令,所谓的指令其实就是电路路线。

参考

draveness 大佬

指令集

go build