摘要:靜態單賦值 階段進行優化:消除死代碼,刪除不使用的分支,替換一些常量表達式等等。對於 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” 選項卡中:

在這裏,高亮顯示變量 ab 以及 if 條件表達式,將向我們展示這些行是怎麼變化的。這些代碼也向我們描述了編譯器如何管理 println 函數,該函數被分解爲 4 個步驟:printlock、printint、printnl、printunlock。編譯器會自動爲我們添加一個鎖,並根據參數的類型,調用相關的方法來正確輸出。

在我們的示例中,由於編譯時已知 ab ,所以編譯器可以計算最終結果並將變量標記爲不必要的。通過 opt 優化這部分:

在這裏, v11 已經被添加的 v4v5 所替代,這兩個 v4v5 被標記爲死代碼。然後通過 opt deadcode 將刪除這些代碼。

對於 if 條件, opt 階段將常量 true 標記爲死代碼,然後刪除:

然後,通過將不必要的塊和條件標記爲無效,另一次傳遞將簡化控制流。這些塊稍後將被另一個專用於死代碼的階段刪除

完成所有過程之後,Go 編譯器現在將生成一箇中間彙編代碼

下一階段將把機器碼生成到二進制文件中。

生成機器碼

編譯器的最後一步是生成目標(object)文件,在我們的例子中生成 main.c 。從這個文件中,現在可以使用 objdumptool 對其進行反編譯。下面是一個很好的圖表,由 Grant Seltzer Richman 創建:

您可以在“ Dissecting Go Binaries ”中找到有關對象文件和二進制文件的更多信息。

生成目標文件後,現在可以使用 go tool link 將其直接傳遞給鏈接器,二進制文件將最終就緒。

相關文章