Go 編譯流程

參考資料

階段

Go編譯器由四個(gè)階段組成,可以分為兩類(lèi)

  • frontend前端:這一階段對(duì)源碼進(jìn)行語(yǔ)法解析驱还,并生成AST
  • backend后端:這一階段將把transform the representation of the source code into machine code, 并進(jìn)行數(shù)項(xiàng)優(yōu)化

為了更好地理解每個(gè)階段凸克,讓我們使用如下的示例程序

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 詞法萎战,解析器,語(yǔ)法樹(shù)

第一個(gè)階段非常簡(jiǎn)單直接:

第一階段蚂维,源碼經(jīng)過(guò)詞法分析虫啥、語(yǔ)法解析,對(duì)每個(gè)源碼文件涂籽,都構(gòu)造出相應(yīng)的語(yǔ)法樹(shù)

Lexer首先運(yùn)行,把源代碼轉(zhuǎn)化為詞法單元苔咪。我們可以通過(guò)這個(gè)程序來(lái)自己模擬運(yùn)行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"

一旦經(jīng)過(guò)詞法化敞恋,源碼被解析構(gòu)造成語(yǔ)法樹(shù)顽分。

語(yǔ)法樹(shù)還包含了代碼位置信息丝里,該信息可用于debug或錯(cuò)誤報(bào)告杯聚。

P2 類(lèi)型檢查和AST轉(zhuǎn)化

  • cmd/compile/internal/gc 創(chuàng)建編譯器AST抒痒,類(lèi)型檢查,AST轉(zhuǎn)換

AST是類(lèi)型檢查的故响。第一個(gè)步驟就是名字解析和類(lèi)型推斷彩届,確定對(duì)象和標(biāo)識(shí)符的對(duì)應(yīng)關(guān)系,表達(dá)式是何種類(lèi)型樟蠕。Type-checking這一階段還引入了額外的確定性步驟,例如吓懈,“聲明未使用”靡狞、函數(shù)是否終止等。

還有一些確定的轉(zhuǎn)換也在AST階段完成榕栏。一些節(jié)點(diǎn)會(huì)根據(jù)類(lèi)型信息進(jìn)行細(xì)化蕾各,比如字符串加法從算術(shù)加法節(jié)點(diǎn)中分離出來(lái)。其他一些示例是不可達(dá)代碼清除妨托、內(nèi)聯(lián)函數(shù)調(diào)用、逃逸分析内颗。

轉(zhuǎn)化到AST的步驟可以通過(guò)命令go tool compile -w來(lái)展示出來(lái)敦腔,如果加上-l,則可以禁用內(nèi)聯(lián)找前。在我們的樣例代碼中判族,如果不禁用內(nèi)聯(lián),add方法會(huì)被內(nèi)聯(lián)掉槽惫。我們可以分別使用go tool compile -w example.ogo tool compile -w -l example.o進(jìn)行對(duì)比

禁用了內(nèi)聯(lián)的命令辩撑,會(huì)輸出這樣的AST

image-20210701110847543

沒(méi)禁用內(nèi)聯(lián)的命令則不會(huì)生成,這里可以看出來(lái),編譯器做了內(nèi)聯(lián)的優(yōu)化氓仲。

SSA 生成

SSA 概念

  • cmd/compile/internal/gc AST轉(zhuǎn)化到SSA
  • cmd/compile/internal/ssa SSA階段和規(guī)則

在這個(gè)階段敬扛,AST轉(zhuǎn)化為SSA的格式,這是一種具有特定屬性的更底層的IR谍珊,可以更輕松地在上面進(jìn)行優(yōu)化并最終生成機(jī)器碼急侥。階段應(yīng)用了內(nèi)聯(lián)函數(shù)。這些是編譯器被教導(dǎo)要根據(jù)具體情況用高度優(yōu)化的代碼替換的特殊函數(shù)贝润。在AST到SSA的轉(zhuǎn)換期間铝宵,某些確定的節(jié)點(diǎn)也被降低為更簡(jiǎn)單的組件华畏,使得編譯器的其余部分可以使用它們尊蚁。例如,內(nèi)置的copy函數(shù)被內(nèi)存移動(dòng)取代仑乌、范圍循環(huán)被重寫(xiě)為for循環(huán)叶撒。由于歷史原因,其中一些目前在SSA轉(zhuǎn)換之前發(fā)生压汪,但長(zhǎng)期計(jì)劃是將它們?nèi)恳频竭@里古瓤。

然后,應(yīng)用一系列的穿香、機(jī)器無(wú)關(guān)的階段和規(guī)則绎速。這些不涉及任何的計(jì)算機(jī)架構(gòu),因此可以在任何GOARCH變體上運(yùn)行洒宝。

這些通用的階段包括:不可達(dá)代碼清除萌京、刪除不需要的nil檢查、移除無(wú)用的分支靠瞎。

通用的重寫(xiě)規(guī)則主要涉及表達(dá)式求妹,包括表達(dá)式替換為常量、優(yōu)化乘法和浮點(diǎn)運(yùn)算等丑勤。

SSA code可以用這個(gè)命令dump并展示出來(lái)

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

SSA階段

Go編譯流程

SSA優(yōu)化解析

start Tab上生成了最開(kāi)始的SSA

image-20210701101016958

變量 a 和 b 與 if 條件一起在此處突出顯示法竞,以便我們稍后查看這些行是如何更改的。 代碼還向我們展示了編譯器如何管理 println 函數(shù)薛躬,它被分解為 4 個(gè)步驟:printlock呆细、printintprintnl絮爷、printunlock坑夯。 編譯器會(huì)自動(dòng)為我們加鎖,并根據(jù)參數(shù)的類(lèi)型調(diào)用相關(guān)方法正確打印柜蜈。
在我們的示例中淑履,由于 a 和 b 在編譯時(shí)已知,編譯器可以計(jì)算最終結(jié)果并將變量標(biāo)記為不再需要秘噪。 opt階段 會(huì)優(yōu)化這部分:

image-20210701101445933

這個(gè)階段v7被優(yōu)化計(jì)算成了3指煎。并且接下來(lái),因?yàn)関4和v5已經(jīng)沒(méi)有人聲明使用暖侨,在opt deadcode階段崇渗,v4和v5也會(huì)被清除掉

image-20210701101729496

等待所有階段完成之后京郑,Go編譯器將會(huì)生成中間匯編語(yǔ)言

image-20210701102735403

下一階段會(huì)將匯編語(yǔ)言轉(zhuǎn)換為二進(jìn)制文件

機(jī)器代碼生成

  • cmd/compile/internal/ssa SSA "lowering" 和 特定arch的階段
  • cmd/internal/obj 機(jī)器語(yǔ)言生成

機(jī)器相關(guān)的編譯階段從"lowering"階段開(kāi)始些举,它將通用的值替換成機(jī)器特定的變體。例如驶臊,在 amd64 內(nèi)存操作數(shù)上是可能的,因此可以組合許多加載-存儲(chǔ)操作扛门。

注意這些底層階段執(zhí)行了所有機(jī)器特定的規(guī)則纵寝,所以也應(yīng)用了很多優(yōu)化。

一旦SSA被"lowered"到更特定的目標(biāo)架構(gòu)爽茴,就開(kāi)始執(zhí)行最終的代碼優(yōu)化室奏。這包括另一個(gè)不可達(dá)代碼清除階段、將值更靠近它們的使用者窍奋、移除從未使用的本地變量琳袄、寄存器分配。

還有一部分重要工作包括堆棧幀布局窖逗,它將堆棧偏移分配給局部變量碎紊,以及指針存活分析,它計(jì)算每個(gè) GC 安全點(diǎn)上哪些堆棧上指針是活躍的仗考。

在 SSA 生成階段結(jié)束時(shí),Go 函數(shù)已轉(zhuǎn)換為一系列 obj.Prog 指令权均。 這些被傳遞給匯編器(cmd/internal/obj)锅锨,匯編器將它們轉(zhuǎn)換成機(jī)器代碼并寫(xiě)出最終的目標(biāo)文件。 目標(biāo)文件還將包含反射數(shù)據(jù)必指、導(dǎo)出數(shù)據(jù)和調(diào)試信息恕洲。

我們可以使用go tool objdump $binary來(lái)查看匯編代碼梅割。當(dāng)compile的.o文件生成之后炮捧,可以通過(guò)go tool link來(lái)生成二進(jìn)制可運(yùn)行文件惦银。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市书蚪,隨后出現(xiàn)的幾起案子迅栅,更是在濱河造成了極大的恐慌,老刑警劉巖为流,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件让簿,死亡現(xiàn)場(chǎng)離奇詭異尔当,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)椭迎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)畜号,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人蛮拔,你說(shuō)我怎么就攤上這事替饿∶车洌” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵据过,是天一觀的道長(zhǎng)绳锅。 經(jīng)常有香客問(wèn)我,道長(zhǎng)鳞芙,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮喳坠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘剃幌。我一直安慰自己晾浴,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布敬鬓。 她就那樣靜靜地躺著笙各,像睡著了一般杈抢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上惶楼,一...
    開(kāi)封第一講書(shū)人閱讀 51,688評(píng)論 1 305
  • 那天歼捐,我揣著相機(jī)與錄音,去河邊找鬼贷盲。 笑死,一個(gè)胖子當(dāng)著我的面吹牛铝穷,可吹牛的內(nèi)容都是我干的佳魔。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼宁脊,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼贤姆!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起庐氮,我...
    開(kāi)封第一講書(shū)人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤弄砍,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后慨畸,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體衣式,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡碴卧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了婶博。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片荧飞。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖叹阔,靈堂內(nèi)的尸體忽然破棺而出挠轴,到底是詐尸還是另有隱情,我是刑警寧澤耳幢,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布岸晦,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏委煤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一修档、第九天 我趴在偏房一處隱蔽的房頂上張望碧绞。 院中可真熱鬧,春花似錦吱窝、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至照激,卻和暖如春发魄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背俩垃。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工励幼, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人口柳。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓苹粟,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親跃闹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嵌削,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容