与构建 IR Tree 密切相关的代码存在于两个地方,一个在文件 cmd/compiler/internal/noder/irgen.go 中,是构建逻辑的入口;另一个在 cmd/compiler/internal/typecheck/target.go 中,该文件只定义了一个全局变量 Target, 而该全局变量保存着 IR Tree 构建的全部结果。
func (g *irgen) generate(noders []*noder) {
declLists := make([][]syntax.Decl, len(noders))
Outer:
// Step 1: 处理 import 语句,将对应的包加载并解析成 types.Pkg 对象,本质上是根据 import 语句实例化 typecheck.Target.Imports 字段
for i, p := range noders {
g.pragmaFlags(p.file.Pragma, ir.GoBuildPragma)
for j, decl := range p.file.DeclList {
switch decl := decl.(type) {
case *syntax.ImportDecl:
// 处理 Import 语句
g.importDecl(p, decl)
default:
// 非 Import 语句保存起来留着后续步骤处理
declLists[i] = p.file.DeclList[j:]
continue Outer // no more ImportDecls
}
}
}
// Step 2: 处理全局的类型申明,对于每个全局类型申明,创建一个对等的 ir.Decl 对象并将其存入 typecheck.Target.Decls 中
// 1. 创建 Name 对象
// 2. 将 types2.Type 转换为对应的 types.Type
// 3. 处理该类型的方法,创建对应的 Field 对象
for _, declList := range declLists {
for _, decl := range declList {
switch decl := decl.(type) {
case *syntax.TypeDecl:
g.typeDecl((*ir.Nodes)(&g.target.Decls), decl)
}
}
}
// Step 3: 处理其他类型的申明,即全局常量申明、全局变量申明、函数申明。为每个申明创建出对应的 ir.Node 并保存在 typecheck.Target.Decls 中
for _, declList := range declLists {
// shizhz -
g.target.Decls = append(g.target.Decls, g.decls(declList)...)
}
// Step 4: 注册所有的 builtin 函数到 types.LocalPkg 中
typecheck.DeclareUniverse()
// Step 5: 根据泛型函数使用情况创建函数,该过程叫着泛型函数的实例化,后续会专门介绍
g.stencil()
// Step 6: 去掉所有的泛型函数申明,详见后续关于第5步的介绍
j := 0
for i, decl := range g.target.Decls {
if decl.Op() != ir.ODCLFUNC || !decl.Type().HasTParam() {
g.target.Decls[j] = g.target.Decls[i]
j++
}
}
g.target.Decls = g.target.Decls[:j]
}
注意,方法 irgen.generate() 的参数 []*noder 便是语法分析的结果:AST. 通过上述代码我们基本可以总结出 IR Tree 构建的总体逻辑:遍历 AST 并构建出 IR Tree, 并将所有的处理结果保存在全局变量 typecheck.Target 中。比较特别的是 Step 5 与 Step 6, 这两步将泛型函数的调用完全转换成了普通函数的调用,之后所有源代码中关于泛型的语义就消失了。
下面我们详细探索一下上述各步骤的处理逻辑。
5.4.3 Import 语句
在类型检查章节,我们已经介绍过新类型检查器中有关包(package)的数据结构以及包加载器(Package Loader)的工作原理,但这部分内容当前还没有应用到编译器后续步骤中来,所以在 IR Tree 的构建阶段依然采用的是之前的加载逻辑,其总体逻辑是将一个 import 语句解析成一个 types.Pkg 对象并保存到 typecheck.Target 的 Imports 属性中。
处理 import 语句对应前面的 Step 1, 代码细节这里不再详细展开。
5.4.4 翻译 AST
将 AST 翻译成 IR Tree 的对应上述 Step 2 与 Step 3. 这里我们先看 Step 3 入口函数的代码:
func (g *irgen) decls(decls []syntax.Decl) []ir.Node {
var res ir.Nodes
for _, decl := range decls {
switch decl := decl.(type) {
case *syntax.ConstDecl:
g.constDecl(&res, decl)
case *syntax.FuncDecl:
g.funcDecl(&res, decl)
case *syntax.TypeDecl:
if ir.CurFunc == nil {
continue // already handled in irgen.generate
}
g.typeDecl(&res, decl)
case *syntax.VarDecl:
g.varDecl(&res, decl)
default:
g.unhandled("declaration", decl)
}
}
return res
}
函数通过 switch...case... 对 AST 进行深度优先遍历,并且在遍历过程中根据 AST 的节点构建出对应的 IR Tree 节点。当然 AST 的节点与 IR Tree 的节点并不是一一对应的,例如对于赋值语句 names := make(chan string), AST 与 IR Tree 的结构如下:
上图中椭圆形代表树的节点,线段代表属性名称,矩形代表属性值。图中只显示了重要的属性值。
可见 IR Tree 与 AST 刻画了相同的程序结构,但是 IR Tree 的节点信息更加具体,例如对于 make(chan string), IR Tree 中对应节点的属性就比 AST 中的节点更有表现力。
泛型函数的实例化会改变 IR Tree 的结构及内容,一是创建出了新的全局函数申明,需要将其添加到 IR Tree 中,二是函数调用的节点需要修改,三是再完成实例化后,泛型函数的申明就不再需要了,即我们可以从 IR Tree 中将其删除。前两点在 Step 5 中通过函数 irgen.stencil() 完成,所有的相关逻辑都定义在文件 cmd/compile/internal/stencil.go 中。而最后一步 Step 6 则删除了 IR Tree 中所有的泛型函数申明。