golang版本: 1.13.1
类型检查
现在,我们走到了golang编译器前端(front-end)的最后一步,类型检查。这次,我们的阅读入口是\cmd\compile\main.go,这就是实际编译器的入口。
定义:
类型检查指验证操作接收的是否为合适的类型数据以及赋值是否合乎类型要求。最自然的方式是认为检查发生在运行时,即当涉及到具体的数据值时,即动态类型检查(即运行时检查)。编译时检查(即静态检查)通过对程序的静态分析,检查所有涉及值的使用的操作、调用和赋值,在程序运行前排除潜在的类型错误。类型检查需基于一定的环境,也就是要在一定的范围内确定类型说明与应用的正确与否,这与标识符的作用域关系密切。
测试方法
本人使用的工具是goland,需要配置一下几项
Environnent: GOSSAFUNC=main // 主函数
Program arguments: aaa.go // 要编译的文件名复制代码
小技巧:
- 由于内部递归很多,而且是一个大的switch,所以可以watch n.Op 来辅助阅读源码
具体过程
首先,我们从入口main进去,主要逻辑实在gc.Main(archInit)里面,大致的分析下Main主要过程:
- 解析命令,初始化数据
- lines := parseFiles(flag.Args()) 读取编译的文件,在这里调用parser,然后调用node(),把syntax tree转成AST,node()里的逻辑会把import的内容转成AST里会调用节点的pkg,所以实际生成的AST是不包括import里的内容的,import会在后面的Inlining阶段的visitBottomUp里进行引用包的编译
- 阶段1:常量,类型以及函数的名称和类型的类型检查
- 阶段2 一些直接的赋值操作,和别名检查
- 阶段3 func的的body内容的检查
- 阶段4:确定如何捕获封闭变量。匿名函数
- 阶段5 初始化操作,读取import
- 阶段6 逃逸分析
- 阶段7:闭包的转换,例如在函数内有使用未声明的参数,回当作参数传进去,在transformclosure函数内有例子
- 阶段8 ssa的一些初始化操作,ssa的compile
- 阶段9:检查外部声明,checkMapKeys()
- flush
以上,就是大致的过程,接下来,我们通过一段代码的编译来看下具体过程
package main
import "fmt"
func main() {
func() {
fmt.Println(456)
}()
fmt.Println(123)
}复制代码
下面,是以func的编译所画的大致流程图。首先通过node()转成AST,然后phase 1是检查func的函数名,phase 3是检查func的body内容,phase 5是初始化操作,phase 6和phase 7是闭包的一些检查,其中,核心检查是在typecheck1()的一个大的switch里,他会对每个node项进行类型检查及他子项做递归处理。
typecheck1() 主要逻辑:
运行结果:
这一阶段主要是看xtop的值
总结
在这个阶段其实有很多东西可以进行深入的去了解,像node()的过程,闭包的具体检查,import的引入等等,但是能力有限,精力有限,所以就先走大致的流程,一些细节就之后在具体的去看。
有疑问加站长微信联系(非本文作者)