Go 编译流程

参考资料

阶段

Go编译器由四个阶段组成,可以分为两类

  • frontend前端:这一阶段对源码进行语法解析,并生成AST
  • backend后端:这一阶段将把transform the representation of the source code into machine code, 并进行数项优化

为了更好地理解每个阶段,让我们使用如下的示例程序

package main

func main() {
    a := 1
    b := 2
    if true {
        add(a, b)
    }
}

func add(a, b int) {
    println(a + b)
}

P1 解析

  • cmd/compile/internal/syntax 词法,解析器,语法树

第一个阶段非常简单直接:

第一阶段,源码经过词法分析、语法解析,对每个源码文件,都构造出相应的语法树

Lexer首先运行,把源代码转化为词法单元。我们可以通过这个程序来自己模拟运行Lexer

package main

import (
    "fmt"
    "go/scanner"
    "go/token"
    "io/ioutil"
)

func main() {
    // src is the input that we want to tokenize.
    src, _ := ioutil.ReadFile(`main.go`)

    // Initialize the scanner
    var s scanner.Scanner
    // positions are relative to fSet
    fSet := token.NewFileSet()
    file := fSet.AddFile("", fSet.Base(), len(src))
    // nil means no error handler
    s.Init(file, src, nil, scanner.ScanComments)

    // Repeated calls to Scan yield the token sequence found in the input
    for {
        pos, tok, lit := s.Scan()
        if tok == token.EOF {
            break
        }
        fmt.Printf("%s\t%s\t%q\n", fSet.Position(pos), tok, lit)
    }
}

截选输出如下:

1:1 package "package"
1:9 IDENT   "main"
1:13    ;   "\n"
3:1 func    "func"
3:6 IDENT   "main"
3:10    (   ""
3:11    )   ""
3:13    {   ""
4:2 IDENT   "a"
4:4 :=  ""
4:7 INT "1"
4:8 ;   "\n"
5:2 IDENT   "b"
5:4 :=  ""
5:7 INT "2"
5:8 ;   "\n"
6:2 if  "if"
6:5 IDENT   "true"
6:10    {   ""
7:3 IDENT   "add"
7:6 (   ""
7:7 IDENT   "a"

一旦经过词法化,源码被解析构造成语法树。

语法树还包含了代码位置信息,该信息可用于debug或错误报告。

P2 类型检查和AST转化

  • cmd/compile/internal/gc 创建编译器AST,类型检查,AST转换

AST是类型检查的。第一个步骤就是名字解析和类型推断,确定对象和标识符的对应关系,表达式是何种类型。Type-checking这一阶段还引入了额外的确定性步骤,例如,“声明未使用”、函数是否终止等。

还有一些确定的转换也在AST阶段完成。一些节点会根据类型信息进行细化,比如字符串加法从算术加法节点中分离出来。其他一些示例是不可达代码清除、内联函数调用、逃逸分析。

转化到AST的步骤可以通过命令go tool compile -w来展示出来,如果加上-l,则可以禁用内联。在我们的样例代码中,如果不禁用内联,add方法会被内联掉。我们可以分别使用go tool compile -w example.ogo tool compile -w -l example.o进行对比

禁用了内联的命令,会输出这样的AST

image-20210701110847543

没禁用内联的命令则不会生成,这里可以看出来,编译器做了内联的优化。

SSA 生成

SSA 概念

  • cmd/compile/internal/gc AST转化到SSA
  • cmd/compile/internal/ssa SSA阶段和规则

在这个阶段,AST转化为SSA的格式,这是一种具有特定属性的更底层的IR,可以更轻松地在上面进行优化并最终生成机器码。阶段应用了内联函数。这些是编译器被教导要根据具体情况用高度优化的代码替换的特殊函数。在AST到SSA的转换期间,某些确定的节点也被降低为更简单的组件,使得编译器的其余部分可以使用它们。例如,内置的copy函数被内存移动取代、范围循环被重写为for循环。由于历史原因,其中一些目前在SSA转换之前发生,但长期计划是将它们全部移到这里。

然后,应用一系列的、机器无关的阶段和规则。这些不涉及任何的计算机架构,因此可以在任何GOARCH变体上运行。

这些通用的阶段包括:不可达代码清除、删除不需要的nil检查、移除无用的分支。

通用的重写规则主要涉及表达式,包括表达式替换为常量、优化乘法和浮点运算等。

SSA code可以用这个命令dump并展示出来

GOSSAFUNC=main go tool compile main.go && open ssa.html

SSA阶段

Go编译流程

SSA优化解析

start Tab上生成了最开始的SSA

image-20210701101016958

变量 a 和 b 与 if 条件一起在此处突出显示,以便我们稍后查看这些行是如何更改的。 代码还向我们展示了编译器如何管理 println 函数,它被分解为 4 个步骤:printlockprintintprintnlprintunlock。 编译器会自动为我们加锁,并根据参数的类型调用相关方法正确打印。
在我们的示例中,由于 a 和 b 在编译时已知,编译器可以计算最终结果并将变量标记为不再需要。 opt阶段 会优化这部分:

image-20210701101445933

这个阶段v7被优化计算成了3。并且接下来,因为v4和v5已经没有人声明使用,在opt deadcode阶段,v4和v5也会被清除掉

image-20210701101729496

等待所有阶段完成之后,Go编译器将会生成中间汇编语言

image-20210701102735403

下一阶段会将汇编语言转换为二进制文件

机器代码生成

  • cmd/compile/internal/ssa SSA "lowering" 和 特定arch的阶段
  • cmd/internal/obj 机器语言生成

机器相关的编译阶段从"lowering"阶段开始,它将通用的值替换成机器特定的变体。例如,在 amd64 内存操作数上是可能的,因此可以组合许多加载-存储操作。

注意这些底层阶段执行了所有机器特定的规则,所以也应用了很多优化。

一旦SSA被"lowered"到更特定的目标架构,就开始执行最终的代码优化。这包括另一个不可达代码清除阶段、将值更靠近它们的使用者、移除从未使用的本地变量、寄存器分配。

还有一部分重要工作包括堆栈帧布局,它将堆栈偏移分配给局部变量,以及指针存活分析,它计算每个 GC 安全点上哪些堆栈上指针是活跃的。

在 SSA 生成阶段结束时,Go 函数已转换为一系列 obj.Prog 指令。 这些被传递给汇编器(cmd/internal/obj),汇编器将它们转换成机器代码并写出最终的目标文件。 目标文件还将包含反射数据、导出数据和调试信息。

我们可以使用go tool objdump $binary来查看汇编代码。当compile的.o文件生成之后,可以通过go tool link来生成二进制可运行文件。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容