Go 編譯器概述
摘要:靜態單賦值 階段進行優化:消除死代碼,刪除不使用的分支,替換一些常量表達式等等。對於 if 條件, opt 階段將常量 true 標記爲死代碼,然後刪除:。
本文基於 Go 1.13
Go 編譯器是 Go 生態系統中的一個重要工具,因爲它是將程序構建爲可執行二進制文件的基本步驟之一。編譯器的歷程是漫長的,它先用 C 語言編寫,遷移到 Go,許多優化和清理將在未來繼續發生,讓我們來看看它的高級操作。
階段(phases)
Go 編譯器由四個階段組成,可以分爲兩類:
- 前端(frontend):這個階段從源代碼進行分析,並生成一個抽象的源代碼語法結構,稱爲 AST
- 後端(backend):第二階段將把源代碼的表示轉換爲機器碼,並進行一些優化。
爲了更好理解每個階段,我們看個簡單的程序:
package main func main() { a := 1 b := 2 if true { add(a, b) } } func add(a, b int) { println(a + b) }
解析
第一階段非常簡單,在 文檔 中有很好的解釋:
在編譯的第一階段,對源代碼進行標記(詞法分析)、解析(語法分析),併爲每個源文件構建語法樹。
lexer 是第一個運行用來標記源代碼的包。下面是上邊例子的 標記化 輸出:
一旦被標記化,代碼將被解析、構建代碼樹。
AST(抽象語法樹) 轉換
可以通過 go tool compile
命令和標誌 -w
展示 抽象語法樹
的轉換:
此階段還將包括內聯等優化。在我們的示例中,由於我們沒有看到 CALLFUNC
該方法的任何 add
指令,該方法 add
已經內聯。讓我們使用禁用內聯的標誌 -l
再次運行。
AST 生成後,它允許編譯器使用 SSA 表示轉到較低級別的中間表示。
SSA(靜態單賦值)的生成
靜態單賦值 階段進行優化:消除死代碼,刪除不使用的分支,替換一些常量表達式等等。
使用 GOSSAFUNC=main Go tool compile main.go && open ssa.html
命令,生成 HTML 文檔的命令將在 SSA 包中完成所有不同的過程,因此可以轉儲 SSA 代碼:
生成的 SSA 位於 “start” 選項卡中:
在這裏,高亮顯示變量 a
和 b
以及 if
條件表達式,將向我們展示這些行是怎麼變化的。這些代碼也向我們描述了編譯器如何管理 println
函數,該函數被分解爲 4 個步驟:printlock、printint、printnl、printunlock。編譯器會自動爲我們添加一個鎖,並根據參數的類型,調用相關的方法來正確輸出。
在我們的示例中,由於編譯時已知 a
和 b
,所以編譯器可以計算最終結果並將變量標記爲不必要的。通過 opt
優化這部分:
在這裏, v11
已經被添加的 v4
和 v5
所替代,這兩個 v4
和 v5
被標記爲死代碼。然後通過 opt deadcode
將刪除這些代碼。
對於 if
條件, opt
階段將常量 true
標記爲死代碼,然後刪除:
然後,通過將不必要的塊和條件標記爲無效,另一次傳遞將簡化控制流。這些塊稍後將被另一個專用於死代碼的階段刪除
完成所有過程之後,Go 編譯器現在將生成一箇中間彙編代碼
下一階段將把機器碼生成到二進制文件中。
生成機器碼
編譯器的最後一步是生成目標(object)文件,在我們的例子中生成 main.c
。從這個文件中,現在可以使用 objdumptool
對其進行反編譯。下面是一個很好的圖表,由 Grant Seltzer Richman 創建:
您可以在“ Dissecting Go Binaries ”中找到有關對象文件和二進制文件的更多信息。
生成目標文件後,現在可以使用 go tool link
將其直接傳遞給鏈接器,二進制文件將最終就緒。