IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    通过实例理解Go静态单赋值(SSA)

    bigwhite发表于 2022-10-21 14:53:50
    love 0

    本文永久链接 – https://tonybai.com/2022/10/21/understand-go-ssa-by-example

    在上一篇文章《通过实例理解Go内联优化》中,我们探讨了Go编译器在编译中端进行的内联优化。内联优化基于IR中间表示进行,不过Go编译过程不止有一种IR表示,这点和龙书《编译原理(第二版)》的在第六章“中间代码生成”一开始处的讲解是一致的,即在将给定源语言的一个程序翻译成特定的目标机器代码的过程中,一个编译器可能构造出一系列中间表示(IR),如下图:

    高层中间表示更接近于源语言,而低层的中间表示则更接近于目标机器。在Go编译过程中,如果说内联优化使用的IR是高层中间表示,那么低层中间表示非支持静态单赋值(SSA)的中间代码形式莫属。

    在这一篇中,我们将沿着Go编译器的后端优化之路继续走下去,我们来认识一下静态单赋值(SSA)。

    1. 静态单赋值(SSA)的历史

    静态单赋值(Static Single Assignment,SSA),也有称为Single Static Assignment的,是一种中间代码的表示形式(IR),或者说是某种中间代码所具备的属性,它是由IBM的三位研究员:Barry K. Rosen、Mark N. Wegman和F. Kenneth Zadeck于1988年提出的。

    具有SSA属性的IR都具有这样的特征:

    • 每个变量在使用前都需要被定义
    • 每个变量被精确地赋值一次(使得一个变量的值与它在程序中的位置无关)

    下面是一个简单的例子(伪代码):

    y = 1
    y = 2
    x = y
    

    转换为SSA形式为:

    y1 = 1
    y2 = 2
    x1 = y2
    

    我们看到由于SSA要求每个变量只能赋值一次,因此在转换为SSA后,变量y用y1和y2来表示,后面的序号越大,表明y的版本越新。从这一段三行的代码我们也可以看到,在SSA层面,y1 = 1这行代码就是一行死代码(dead code),即对结果不会产生影响的代码,可以在中间代码优化时被移除掉。

    1991年,同样来自IBM研究院的Ron Cytron和Jeanne Ferrante以及前面的三位研究员又一起给出了构建SSA的快速算法,这进一步推动了SSA在编译器领域的快速应用。

    SSA的提出以及后续的流行正是因为SSA形式中间代码具有很好的优化空间,基于SSA可以开启一些新的编译器优化算法或增强现有的优化算法,因此自SSA提出后,各种主流语言编译器后端均逐渐开始支持SSA,包括GCC、llvm、hotspot JVM、v8 js等。SSA也成为了一种IR表示的事实标准。

    那么Go语言是何时开始与SSA结缘的呢?我们继续往下看。

    2. Go与SSA

    相对于GCC、LLVM,Go编译器还相对年轻,因此SSA加入Go的时间还不算太长。

    Go SSA的工作始于Go 1.5版本实现自举之前,2015年2月初,负责编译器后端的Go团队核心成员的Keith Randall博士就在golang-dev google group上提出要让Go支持SSA的工作计划:

    “我想从目前基于语法树的IR转换到更现代的基于SSA的IR。有了SSA IR,我们可以实现很多在当前编译器中难以做到的优化” - Keith Randall
    

    同期,Keith Randall博士还编写了“New SSA Backend for the Go Compiler”文档,具体介绍了Go要支持SSA的理由以及分几步走的实现方案。

    在为什么选择自己实现SSA IR,而不是转换为当时现成的诸如gcc, llvm等支持的IR形式并利用成熟后端进行中间代码优化这个问题上,Keith Randall博士给出了三点理由:

    • 从Go编译速度考虑:Go团队和社区对编译速度有着格外的青睐,Randall的目标是设计一个线性时间的SSA算法,实现快速SSA优化,但gcc, llmv等IR显然没有在速度方面给予额外的考虑;

    • 从功能完整性上考虑:Go运行时需要精确的栈帧地图(the map of stack frame),用来支持GC和栈拷贝,这些在gcc, llvm中都不会提供;

    • 从Go核心开发者的编译器使用体验方面考虑:如果使用llvm、gcc等ir,显然Go核心开发人员在编译go的时候还需要依赖llvm或gcc,这种额外的依赖对他们来说很难说是体验友好的。

    2016年3月1日,在Go 1.7版本的master分支提交权限刚刚打开之后,Keith Randall就将支持ssa的dev.ssa分支合并到Go项目主线中了。

    在Go 1.7版本中,Go正式支持SSA,不过由于时间有限,Go 1.7 SSA仅支持针对amd64架构的优化。即便如此,Go支持SSA后,Keith Randall的benchmark显示性能提升12%,代码段缩小13%:


    图:go 1.7 benchmark(图来自keith博士的slide)

    Go 1.7正式发布时,其发布文档称Go程序的性能因对SSA的支持而提升5%-35%以上。由此看,Go SSA的实现达到了Keith Randall博士的预期目标,也为Go编译器后续的持续优化奠定了基础。

    在2017年2月发布的Go 1.8版本中,Go SSA的支持范围扩展到其他所有Go支持的cpu架构,包括arm和arm64、mips和mips64、ppc64等。

    了解了Go SSA的演进后,我们再来简单说说Go编译器中SSA的实现。

    3. 转换为SSA

    我们先来看看转换为SSA以及SSA优化在编译过程中所处的位置:


    图:Go SSA所处的环节(图来自keith博士的slide)

    上图是keith博士在2017年gophercon大会上slide中的一幅图,这幅图中明确了生成SSA形式以及SSA优化所处的环节。不过较新的Go版本中,convert to SSA之前也有一种不同于最初的抽象语法树的ir(比如:Go 1.19),SSA是由此种ir转换过来的。

    从代码上来看,ir到SSA形式的转换发生在下面环节(Go 1.19版本代码,其他版本可能代码位置和内容均由不同):

    // $GOROOT/src/cmd/compile/internal/gc/main.go
    func Main(archInit func(*ssagen.ArchInfo)) {
        base.Timer.Start("fe", "init")
    
        defer handlePanic()
    
        archInit(&ssagen.Arch)
        ... ...
    
        // Compile top level functions.
        // Don't use range--walk can add functions to Target.Decls.
        base.Timer.Start("be", "compilefuncs")
        fcount := int64(0)
        for i := 0; i < len(typecheck.Target.Decls); i++ {
            if fn, ok := typecheck.Target.Decls[i].(*ir.Func); ok {
                // Don't try compiling dead hidden closure.
                if fn.IsDeadcodeClosure() {
                    continue
                }
                enqueueFunc(fn)
                fcount++
            }
        }
        base.Timer.AddEvent(fcount, "funcs")
    
        compileFunctions()
    
        ... ...
    }
    

    在Main中,我们看到代码会将所有Target.Decls(函数)通过enqueueFunc入队列(compilequeue),然后调用compileFunctions来实现各个函数从AST ir到SSA形式的转换,compileFunctions在compile.go中,其实现如下:

    // $GOROOT/src/cmd/compile/internal/gc/compile.go
    func compileFunctions() {
        if len(compilequeue) == 0 {
            return
        }
    
        ... ...
        // By default, we perform work right away on the current goroutine
        // as the solo worker.
        queue := func(work func(int)) {
            work(0)
        }
        ... ...
    
        var compile func([]*ir.Func)
        compile = func(fns []*ir.Func) {
            wg.Add(len(fns))
            for _, fn := range fns {
                fn := fn
                queue(func(worker int) {
                    ssagen.Compile(fn, worker)
                    compile(fn.Closures)
                    wg.Done()
                })
            }
        }
        types.CalcSizeDisabled = true // not safe to calculate sizes concurrently
        base.Ctxt.InParallel = true
    
        compile(compilequeue)
        ... ...
    }
    

    在compileFunctions中我们看到,编译器从compilequeue取出AST IR形式的函数,并调用ssagen.Compile将其编译为SSA形式。下面是ssagen.Compile的代码:

    // $GOROOT/src/cmd/compile/internal/ssagen/pgen.go
    
    // Compile builds an SSA backend function,
    // uses it to generate a plist,
    // and flushes that plist to machine code.
    // worker indicates which of the backend workers is doing the processing.
    func Compile(fn *ir.Func, worker int) {
        f := buildssa(fn, worker)
        // Note: check arg size to fix issue 25507.
        if f.Frontend().(*ssafn).stksize >= maxStackSize || f.OwnAux.ArgWidth() >= maxStackSize {
            largeStackFramesMu.Lock()
            largeStackFrames = append(largeStackFrames, largeStack{locals: f.Frontend().(*ssafn).stksize, args: f.OwnAux.ArgWidth(), pos: fn.Pos()})
            largeStackFramesMu.Unlock()
            return
        }
        pp := objw.NewProgs(fn, worker)
        defer pp.Free()
        genssa(f, pp)
        // Check frame size again.
        // The check above included only the space needed for local variables.
        // After genssa, the space needed includes local variables and the callee arg region.
        // We must do this check prior to calling pp.Flush.
        // If there are any oversized stack frames,
        // the assembler may emit inscrutable complaints about invalid instructions.
        if pp.Text.To.Offset >= maxStackSize {
            largeStackFramesMu.Lock()
            locals := f.Frontend().(*ssafn).stksize
            largeStackFrames = append(largeStackFrames, largeStack{locals: locals, args: f.OwnAux.ArgWidth(), callee: pp.Text.To.Offset - locals, pos: fn.Pos()})
            largeStackFramesMu.Unlock()
            return
        }
    
        pp.Flush() // assemble, fill in boilerplate, etc.
        // fieldtrack must be called after pp.Flush. See issue 20014.
        fieldtrack(pp.Text.From.Sym, fn.FieldTrack)
    }
    

    这里贴出了Compile的完整实现,Compile函数中真正负责生成具有SSA属性的中间代码的是buildssa函数,看了一下buildssa函数有近300行代码,有点复杂,这里挑挑拣拣,把主要的调用摘录出来:

    // $GOROOT/src/cmd/compile/internal/ssagen/ssa.go
    
    // buildssa builds an SSA function for fn.
    // worker indicates which of the backend workers is doing the processing.
    func buildssa(fn *ir.Func, worker int) *ssa.Func {
        name := ir.FuncName(fn)
        ... ...
    
        // Convert the AST-based IR to the SSA-based IR
        s.stmtList(fn.Enter)
        s.zeroResults()
        s.paramsToHeap()
        s.stmtList(fn.Body)
    
        // fallthrough to exit
        if s.curBlock != nil {
            s.pushLine(fn.Endlineno)
            s.exit()
            s.popLine()
        }
        ... ...
    
        // Main call to ssa package to compile function
        ssa.Compile(s.f)
        ... ...
    }
    

    buildssa中的ssa.Compile咱们后续再看,那个涉及到SSA的多轮(pass)优化,我们看一下从基于AST形式的IR到基于SSA形式的IR的转换,无论是fn.Enter还是fn.Body,本质都是一组ir Node,stmtList将这些node逐个转换为SSA形式。Go提供了可视化的ssa dump工具,我们可以更直观的来看一下。

    Go语言隶属于命令式编程语言(imperative programming language),这类编程范式有三大典型控制结构:顺序结构、选择结构和循环结构,我们先来看看一个最简单的顺序结构是如何翻译为SSA的:

    // github.com/bigwhite/experiments/tree/master/ssa-examples/sequential.go
    
    package main
    
    func sum(a, b, c int) int {
        d := a + b
        e := d + c
        return e
    }
    
    func main() {
        println(sum(1, 2, 3))
    }
    

    我们通过下面命令来生成函数sum的SSA转换过程:

    $GOSSAFUNC=sum go build sequential.go
    dumped SSA to ./ssa.html
    $mv ssa.html ssa-sequential.html
    $open ./ssa-sequential.html
    

    上面的open命令会在本地打开浏览器并显示ssa-sequential.html页面:

    上图中,最左侧是源码(源码显示两次,感觉是bug),中间的是AST形式的IR,最右侧的框框中就是Go编译器生成的第一版SSA,为了更好说明,我们将其贴到下面来:

    // github.com/bigwhite/experiments/tree/master/ssa-examples/ssa-sequential.html
    
    b1:-
      v1 (?) = InitMem <mem>
      v2 (?) = SP <uintptr>
      v3 (?) = SB <uintptr>
      v4 (?) = LocalAddr <*int> {a} v2 v1
      v5 (?) = LocalAddr <*int> {b} v2 v1
      v6 (?) = LocalAddr <*int> {c} v2 v1
      v7 (?) = LocalAddr <*int> {~r0} v2 v1
      v8 (3) = Arg <int> {a} (a[int])
      v9 (3) = Arg <int> {b} (b[int])
      v10 (3) = Arg <int> {c} (c[int])
      v11 (?) = Const64 <int> [0]
      v12 (+4) = Add64 <int> v8 v9 (d[int])
      v13 (+5) = Add64 <int> v12 v10 (e[int])
      v14 (+6) = MakeResult <int,mem> v13 v1
    Ret v14 (+6)
    
    name a[int]: v8
    name b[int]: v9
    name c[int]: v10
    name d[int]: v12
    name e[int]: v13
    

    从结构上来看,SSA分为两部分,一部分是由b1、Ret组成的blocks,另一部分则是命名变量与SSA value的对应关系。

    在SSA中,一个block代表了一个函数控制流图(control flow graph)中的基本代码块(basic block),从代码注释中可以看到SSA有四种block类型:Plain,If、Exit和Defer:

    // $GOROOT/src/cmd/compile/internal/ssa/block.go
    
    // BlockKind is the kind of SSA block.
    //
    //    kind          controls        successors
    //  ------------------------------------------
    //    Exit      [return mem]                []
    //   Plain                []            [next]
    //      If   [boolean Value]      [then, else]
    //   Defer             [mem]  [nopanic, panic]  (control opcode should be OpStaticCall to runtime.deferproc)
    type BlockKind int16
    

    但实际的BlockKind已经与注释不一致了,opGen.go是一个自动生成的文件,其中的BlockKind类型的常量值有数十个,即便滤掉CPU架构相关的常量,剩下的还有8个(从BlockPlain到BlockFirst):

    // $GOROOT/src/cmd/compile/internal/ssa/opGen.go
    
    const (
        BlockInvalid BlockKind = iota
        ... ...
    
        BlockPlain
        BlockIf
        BlockDefer
        BlockRet
        BlockRetJmp
        BlockExit
        BlockJumpTable
        BlockFirst
    )
    

    上面的sum函数的SSA代码例子中,b1应该就是Plain类型的,Ret显然是BlockRet类型。

    Plain类型的Block中是一组values,value是SSA的基本构成要素。根据SSA的定义,一个value只能被精确地定义一次,但是它可以被使用任意多次。如示例,一个value主要包括一个唯一的标识符,一个操作符,一个类型和一些参数,下面的Value类型的LongString和LongHTML方法返回的字符串更能说明Value的格式。尤其是LongHTML方法就是输出ssa html中内容的方法:

    // $GOROOT/src/cmd/compile/internal/ssa/value.go
    
    // long form print.  v# = opcode <type> [aux] args [: reg] (names)
    func (v *Value) LongString() string {
        ... ...
    }
    
    // $GOROOT/src/cmd/compile/internal/ssa/html.go
    func (v *Value) LongHTML() string {
        // TODO: Any intra-value formatting?
        // I'm wary of adding too much visual noise,
        // but a little bit might be valuable.
        // We already have visual noise in the form of punctuation
        // maybe we could replace some of that with formatting.
        s := fmt.Sprintf("<span class=\"%s ssa-long-value\">", v.String())
    
        linenumber := "<span class=\"no-line-number\">(?)</span>"
        if v.Pos.IsKnown() {
            linenumber = fmt.Sprintf("<span class=\"l%v line-number\">(%s)</span>", v.Pos.LineNumber(), v.Pos.LineNumberHTML())
        }
    
        s += fmt.Sprintf("%s %s = %s", v.HTML(), linenumber, v.Op.String())
    
        s += " &lt;" + html.EscapeString(v.Type.String()) + "&gt;"
        s += html.EscapeString(v.auxString())
        for _, a := range v.Args {
            s += fmt.Sprintf(" %s", a.HTML())
        }
        r := v.Block.Func.RegAlloc
        if int(v.ID) < len(r) && r[v.ID] != nil {
            s += " : " + html.EscapeString(r[v.ID].String())
        }
        var names []string
        for name, values := range v.Block.Func.NamedValues {
            for _, value := range values {
                if value == v {
                    names = append(names, name.String())
                    break // drop duplicates.
                }
            }
        }
        if len(names) != 0 {
            s += " (" + strings.Join(names, ", ") + ")"
        }
    
        s += "</span>"
        return s
    }
    
    

    以例子中的v12这一个value为例:

      v12 (+4) = Add64 <int> v8 v9 (d[int])
    
    • v12是该value的唯一标识符,其中的12为ID,ID是从1开始的整数;
    • (+4)是对应的源码的行号;
    • Add64是操作符;
    • 是value的类型(v.Type());
    • v8, v9则是Add64操作符的参数;
    • (d[int])是v12对应的LocalSlot,LocalSlot代表栈帧上的一个位置(location),用来识别和存储输出参数、输出参数或其他变量node。

    ssa dump输出的另一部分则是命名变量与SSA value的对应关系,其格式也是:name LocalSlot: value:

    name a[int]: v8
    name b[int]: v9
    name c[int]: v10
    name d[int]: v12
    name e[int]: v13
    

    输出上述第二部分的代码如下:

    // $GOROOT/src/cmd/compile/internal/ssa/print.go
    func (p stringFuncPrinter) named(n LocalSlot, vals []*Value) {
        fmt.Fprintf(p.w, "name %s: %v\n", n, vals)
    }
    

    顺序结构的代码执行流是从上到下的,每个block后面仅有一个后继block,这样的SSA转换较为好理解。

    下面我们再来看看一个选择控制结构 – if控制语句的ssa,下面是我们的示例Go源码:

    // github.com/bigwhite/experiments/tree/master/ssa-examples/selection_if.go
    
    package main
    
    func foo(b bool) int {
        if b {
            return 2
        }
        return 3
    }
    
    func main() {
        println(foo(true))
    }
    

    我们通过下面命令输出函数foo的SSA中间代码:

    $GOSSAFUNC=foo go build selection_if.go
    dumped SSA to ./ssa.html
    $mv ssa.html ssa-selection-if.html
    $open ./ssa-selection-if.html
    

    open命令启动浏览器显示foo函数的SSA形式:

    有了上面关Go SSA格式的基础,这段SSA代码分析起来就容易一些了。

    这段SSA中有多个block,包括plain block、if block、ret block等。我们重点关注SSA对if语句的处理。

    经典SSA转换理论中,SSA将if分支转换为带有Φ函数的SSA代码(如下图):


    图:if语句的SSA转换(图来自keith博士的slide)

    Φ函数(希腊字母fài)是代码中的一个merge point,它可以将其前置的n个block的执行路径汇聚在一起。不过它仅用于代码分析使用,最终生成的代码中并不会有Φ函数的存在。关于在何处插入Φ函数等算法太理论了,这里就不展开了。

    我们看看现实中go针对if语句的处理:

    b1:
      v1 (?) = InitMem <mem>
      v2 (?) = SP <uintptr>
      v3 (?) = SB <uintptr>
      v4 (?) = LocalAddr <*bool> {b} v2 v1
      v5 (?) = LocalAddr <*int> {~r0} v2 v1
      v6 (3) = Arg <bool> {b} (b[bool])
      v7 (?) = Const64 <int> [0]
      v8 (?) = Const64 <int> [2]
      v11 (?) = Const64 <int> [3]
    If v6 → b3 b2 (4)
    
    b2: ← b1
      v13 (7) = Copy <mem> v1
      v12 (7) = MakeResult <int,mem> v11 v13
    Ret v12 (+7)
    
    b3: ← b1
      v10 (5) = Copy <mem> v1
      v9 (5) = MakeResult <int,mem> v8 v10
    Ret v9 (+5)
    
    name b[bool]: v6
    

    这里关键是if block,if判断v6即变量b的值,如果为true,代码执行就流向block b3,否则流向block b2。

    下面的b2、b3 block也都包含了前置block的属性,以b2为例,对于来自b1 block的流,执行对应block的代码。基于switch的选择语句更为复杂,有兴趣的朋友可以自己看一下ssa-selection-switch.html。

    我们最后看一下循环结构,下面是Go代码:

    // github.com/bigwhite/experiments/tree/master/ssa-examples/for_loop.go
    package main
    
    func sumN(n int) int {
        var r int
        for i := 1; i <= n; i++ {
            r = r + i
        }
        return r
    }
    
    func main() {
        println(sumN(10))
    }
    

    其生成的SSA如下图:

    我们看到循环结构的ssa block更多,流向更为复杂,如果将其转换为一张图的话,那就应该是这样的:

    我们看到:无论是选择结构还是循环结构,SSA实质上构建了一个函数的控制流图(control flow graph),图中每个节点就是一个block,函数的执行控制流在各个block间转移。而后续基于SSA的优化就是基于block中value的仅赋值一次的特性以及block的控制流图进行的。

    接下来,我们简单看看目前Go基于SSA IR都做了哪些优化。

    4. 基于SSA的多轮(pass)优化

    buildssa函数中ssa.Compile调用执行了基于SSA IR的多轮(passes)优化:

    // $GOROOT/src/cmd/compile/internal/ssa/compile.go
    
    func Compile(f *Func) {
        ... ...
        for _, p := range passes {
            ... ...
            tStart := time.Now()
            p.fn(f)
            tEnd := time.Now()
            ... ...
        }
    }
    

    我们看到,针对某个函数,Compile函数对其安装预置的passes进行多轮优化,都有哪些pass呢?我们来看看:

    // $GOROOT/src/cmd/compile/internal/ssa/compile.go
    
    // list of passes for the compiler
    var passes = [...]pass{
        {name: "number lines", fn-3693: numberLines, required: true},
        {name: "early phielim", fn-3693: phielim},
        {name: "early copyelim", fn-3693: copyelim},
        {name: "early deadcode", fn-3693: deadcode}, // remove generated dead code to avoid doing pointless work during opt
        {name: "short circuit", fn-3693: shortcircuit},
        {name: "decompose user", fn-3693: decomposeUser, required: true},
        {name: "pre-opt deadcode", fn-3693: deadcode},
        ... ...
        {name: "regalloc", fn-3693: regalloc, required: true},   // allocate int & float registers + stack slots
        {name: "loop rotate", fn-3693: loopRotate},
        {name: "stackframe", fn-3693: stackframe, required: true},
        {name: "trim", fn-3693: trim}, // remove empty blocks
    }
    

    粗略数了一下,这里约有50个pass(其中包含多轮的deadcode清理),每个pass执行的代码都位于$GOROOT/src/cmd/compile/internal/ssa目录下,我们也可以通过dump出的html查看每一pass后得到的SSA结果,以ssa-sequential.html为例,其多轮优化的示意图如下:

    点击浏览器页面上的黑体字优化标题(比如:lowered deadcode for cse),这一步产生的SSA代码都会显示出来,最后一个框框中是基于SSA生成目标架构的汇编代码。

    每一个pass都有其独特性,比如cse,代表Common Subexpression Elimination(共同子表达式删除) ,下面是一个cse优化的例子:

    y = x + 5
    ...
    z = x + 5
    

    cse优化后(前提中间过程中x值没变过):

    y = x + 5
    ...
    z = y
    

    在这个示例中,经过一轮cse,Go便可以节省下一次没必要的加法运算(z = x + 5)。别看一次加法运算不起眼,积累多了也是不小的性能提升,

    如果你对某一pass的优化动作感兴趣,可以对照$GOROOT/src/cmd/compile/internal/ssa目录下的代码与浏览器中生成的SSA来对其进行深入研究。

    5. 小结

    编译器后端的逻辑总是很难理解的,本文对Go编译器与SSA的渊源、Go编译器中驱动SSA转换和优化的环节以及Go生成的SSA的形式与过程做了介绍,算是对SSA入了个门。但要想真正搞懂SSA转换以及基于SSA的优化步骤的细节,认真阅读SSA相关的paper和资料(见参考资料)以及相关code是不可或缺的。

    本文涉及的代码在这里可以下载。

    6. 参考资料

    • 《编译原理(第二版)》- https://book.douban.com/subject/3296317/
    • SSA: Static Single-Assignment Form – https://www.slideserve.com/heidi-farmer/ssa-static-single-assignment-form
    • 《Static Single Assignment Book》 – https://pfalcon.github.io/ssabook/latest/book-full.pdf
    • Static single-assignment form – https://en.wikipedia.org/wiki/Static_single_assignment_form
    • GopherCon 2017: Keith Randall – Generating Better Machine Code with SSA – https://about.sourcegraph.com/blog/go/generating-better-machine-code-with-ssa
    • Generating Better Machine Code with SSA(slide) – https://raw.githubusercontent.com/gophercon/2017-talks/master/KeithRandall-GeneratingBetterMachineCodeWithSSA/GeneratingBetterMachineCodeWithSSA.pdf
    • New SSA Backend for the Go Compiler – https://docs.google.com/document/d/1szwabPJJc4J-igUZU4ZKprOrNRNJug2JPD8OYi3i1K0/edit

    “Gopher部落”知识星球旨在打造一个精品Go学习和进阶社群!高品质首发Go技术文章,“三天”首发阅读权,每年两期Go语言发展现状分析,每天提前1小时阅读到新鲜的Gopher日报,网课、技术专栏、图书内容前瞻,六小时内必答保证等满足你关于Go语言生态的所有需求!2022年,Gopher部落全面改版,将持续分享Go语言与Go应用领域的知识、技巧与实践,并增加诸多互动形式。欢迎大家加入!

    img{512x368}
    img{512x368}

    img{512x368}
    img{512x368}

    Gopher Daily(Gopher每日新闻)归档仓库 – https://github.com/bigwhite/gopherdaily

    我的联系方式:

    • 微博:https://weibo.com/bigwhite20xx
    • 博客:tonybai.com
    • github: https://github.com/bigwhite

    商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。

    © 2022, bigwhite. 版权所有.



沪ICP备19023445号-2号
友情链接