与构建 IR Tree 密切相关的代码存在于两个地方,一个在文件 cmd/compiler/internal/noder/irgen.go 中,是构建逻辑的入口;另一个在 cmd/compiler/internal/typecheck/target.go 中,该文件只定义了一个全局变量 Target, 而该全局变量保存着 IR Tree 构建的全部结果。
注意,方法 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 入口函数的代码:
函数通过 switch...case... 对 AST 进行深度优先遍历,并且在遍历过程中根据 AST 的节点构建出对应的 IR Tree 节点。当然 AST 的节点与 IR Tree 的节点并不是一一对应的,例如对于赋值语句 names := make(chan string), AST 与 IR Tree 的结构如下:
AST vs 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 中所有的泛型函数申明。