AST
抽象语法树(Abstract Syntax Tree、AST)
例如2 * 3 + 7
,生成如下 AST:
AST 的作用是?
抹去了源代码中不重要的一些字符 - 空格、分号或者括号等等。编译器在执行完语法分析之后会输出一个抽象语法树,这个抽象语法树会辅助编译器进行语义分析,我们可以用它来确定语法正确的程序是否存在一些类型不匹配的问题。
SSA
静态单赋值(Static Single Assignment、SSA)
如果中间代码具有 SSA 特性,则每个变量只会被赋值一次。
通过下标实现 SSA:
x := 1
x := 2
y := x
经过简单的分析,我们就能够发现上述的代码第一行的赋值语句 x := 1 不会起到任何作用。下面是具有 SSA 特性的中间代码,我们可以清晰地发现变量 y_1 和 x_1 是没有任何关系的,所以在机器码生成时就可以省去 x := 1 的赋值,通过减少需要执行的指令优化这段代码:
x_1 := 1
x_2 := 2
y_1 := x_2
编译原理
一般编译器的前端和后端
- 词法与语法分析
- 类型检查和 AST 转换
- 通用 SSA 生成
- 机器代码生成。
词法与语法分析
通过词法解析器(lexer)进行源文件的词法分析:将文件中的字符串序列转化为 Token 序列。
每一个 Go 的源代码文件最终会被归纳成一个 SourceFile 结构:
词法分析会返回一个不包含空格、换行等字符的 Token 序列,例如:SourceFile = PackageClause ";" { ImportDecl ";" } { TopLevelDecl ";" } .
package, json, import, (, io, ), …,
语法解析器会把 Token 序列转换成有意义的结构体,即语法树(AST):
"json.go": SourceFile {
PackageName: "json",
ImportDecl: []Import{
"io",
},
TopLevelDecl: ...
}
每一个 AST 都对应着一个单独的 Go 语言文件,这个抽象语法树中包括当前文件属于的包名、定义的常量、结构体和函数等:
语法解析的过程中发生的任何语法错误都会被语法解析器发现并将消息打印到标准输出上,整个编译过程也会随着错误的出现而被中止。
类型检查
对 AST 中的类型进行检查,主要包括以下类型:
- 常量、类型和函数名及类型;
- 变量的赋值和初始化;
- 函数和闭包的主体;
- 哈希键值对的类型;
- 导入函数体;
- 外部的声明;
通过遍历整个 AST 进行类型检查,以保证节点不存在类型错误,包括检查结构体对接口的实现。
类型检查阶段不止会对节点的类型进行验证,还会展开和改写一些内建的函数,例如 make 关键字在这个阶段会根据子树的结构被替换成 runtime.makeslice 或者 runtime.makechan 等函数。
中间代码生成
编译器会通过 cmd/compile/internal/gc.compileFunctions 编译整个 Go 语言项目中的全部函数,这些函数会在一个编译队列中等待几个 Goroutine 的消费,并发执行的 Goroutine 会将所有函数对应的抽象语法树转换成中间代码。
机器码生成
Go 语言源代码的 src/cmd/compile/internal 目录中包含了很多机器码生成相关的包,不同类型的 CPU 分别使用了不同的包生成机器码,其中包括 amd64、arm、arm64、mips、mips64、ppc64、s390x、x86 和 wasm
词法分析和语法分析
词法分析
lex 词法分析器生成过程
- 定义 lex 描述文件(lex 的描述文件为
xxx.l
)
(匹配规则为正则表达式)
- 使用 lex 命令生成 .c 文件
lex simplego.l
- 编译 .c 文件
cc lex.yy.c -o simplego -ll
(因为 main 函数在 liblex 库中,所以需要添加 -ll 选项)
- 使用词法分析器进行分析
源文件:
输入词法分析器后:cat main.go | ./simplego
经过如上步骤之后,将机器难以理解的具有高级语言语法的字符串转化为 Token 字符串。
go 词法分析器
Go 语言的词法解析是通过 src/cmd/compile/internal/syntax/scanner.go 文件中的 scanner 结构体实现的,这个结构体会持有当前扫描的数据源文件、启用的模式和当前被扫描到的 Token:
src/cmd/compile/internal/syntax/tokens.go 文件中定义了 Go 语言中支持的全部 Token 类型,所有的 token 类型都是正整数。
语法分析
词法分析器输出的结果 — Token 序列是语法分析器的输入。
参考资料
- https://draveness.me/golang/ -《Go语言设计与实现》