10.2.1 SSA

截止到逃逸分析, 编译器内部使用的是树状(Tree-like)结构来表示源代码,并在该结构上进行各种分析及优化操作,因为树状结构与源代码本身是同构的,可以很方便地进行遍历甚至修改源代码结构。但最终运行在目标机器上的程序是一条一条的机器指令构成的指令流,这种线性结构与树状结构大相径庭,因此在编译器后端采用了更加形态更加贴近目标程序的IR来表示源代码,这种结构叫着SSA(Static Single Assignment)

SSA是三地址代码(Three-address Code)的一种变体,SSA要求代码中每个变量只能被赋值一次,并且任何变量在使用之前必须先申明。这是一种极简的代码形式,基于对变量的以上两点约束,编译器可以很安全地对代码进行各种操作及优化,例如如果一个变量被赋值后没有被使用,那么其赋值语句就是可以删掉的。

Go 的编译器后端也使用 SSA 来作为代码的中间表示,函数编译的过程便是先将函数对应的 IR Tree 节点欢转换为 SSA, 再将该 SSA 翻译成目标机器代码。

与之相关的代码主要包含在 cmd/compile/internal/ssagencmd/compile/internal/ssa 中,函数翻译的入口函数是 Compile(f \*ir.Func, worker int), 位于文件 cmd/compile/internal/ssagen/pgen.go 中,参数 f 是待编译的函数,而 worker 是编译器并发处理时的 worker ID.

我们可以通过两个环境变量来 dump 编译器生成的 SSA 结果:

  1. GOSSAFUNC: 指定需要 dump 的函数

  2. GOSSADIR: 指定结果文件的目录,默认为当前目录

我们来看一下如何使用,将如下代码保存至 main.go 中:

package main

type T struct {
    Name string
}

func (this *T) getName() string {
    if this == nil {
        return "<nil>"
    }
    return this.Name
}

func sum(i, j int) int {
    return i + j + 1
}

运行命令: GOSSAFUNC="sum" go build main.go:

dumped SSA to ./ssa.html

runtime.mainmain·f: function main is undeclared in the main package

该命令将函数 sum 的SSA结果写到了 ssa.html 文件中,可以通过浏览器打开查看。也可以在函数明后加一个加号: GOSSAFUNC="sum+" 同时将结果打印到命令行(stdout)。想要 dump 方法的 SSA 也类似,命令 GOSSAFUNC="(*T).getName" go build main.go 将会将方法 getName() 的SSA写入到 ssa.html 中。

最后更新于