Go編譯器簡介【譯】

alphali發表於2019-02-16

趁著元旦休假+春節,嘗試把2018年期間讓我受益的一些文章、問答,翻譯一下。
歡迎指正、討論,希望對你也有所幫助。
原文連結:https://github.com/golang/go/…

構成Go編譯器的關鍵package都包含在cmd/compile目錄。我們從邏輯上把編譯器編譯過程分成四個階段,下文將簡要介紹這四個階段的package列表。
談到編譯器,你可能聽說過類似“前端”、“後端”這樣的字眼。粗略地講,我們也將編譯器的四個階段工作劃分成了前兩個階段和後兩個階段,也就是前端和後端。還有一個詞——”中端“,通常包含在第二個階段中(譯者注:有的編譯器在前端和後端之間引入了一個程式碼優化階段,稱為middle-end,中端。感興趣的讀者可以提前深入瞭解下一般編譯器架構)。
需要注意的是,go/目錄的package和編譯器無關,比如go/types等。由於最初的編譯器是用C語言編寫而成,go/中包含了一些用Go程式碼編寫的工具,如gofmt和vet。
還有一點需要澄清,”gc“代表”Go compiler“,和我們常用來表示垃圾回收的GC沒啥關係。

1.詞法分析和語法分析

cmd/compile/internal/syntax目錄包含了詞法分析、語法分析和語法樹構造部分。編譯器第一階段工作中,對每個原始檔進行詞法、語法分析,並生成AST(抽象語法樹)。
每個語法樹都精確表達了相應的原始碼檔案。樹的節點對應著各個原始檔的元素,例如表示式、宣告語句。語法樹還包含了程式碼位置資訊,為錯誤報告和除錯資訊提供支援。

2.型別檢查和AST轉換

cmd/compile/internal/gc目錄包含了編譯器AST建立、型別檢查、AST轉換部分。這個package中包含了一個從C繼承的AST定義。這個package的第一要事就是把syntax包的語法樹轉換成編譯器支援的AST來表示。這個步驟看起來略顯多餘,在將來的版本中可能會被重構。
接下來要做的是型別檢查。第一步做名字解析和型別推斷,確定物件屬於哪個識別符號以及表示式的型別。型別檢查還包括一些額外操作,例如判斷”宣告但未使用“的變數、確定函式是否會終止。
某些節點型別的細化也會在這部分完成。例如從算術加節點中拆分出字串加、無用程式碼消除、函式呼叫內聯化和逃逸分析。

3.通用SSA

這裡有官方給出的SSA背景介紹。
cmd/compile/internal/gc(SSA轉換)
cmd/compile/internal/ssa(SSA pass和規則)
這個階段,AST將轉換為靜態單賦值(SSA)形式。這是一種具有特定屬性的低階中間表示法,更容易優化和生成機器碼。
轉換期間還要處理內建函式。現代編譯器經過多代進化已經很智慧,會用大量優化程式碼替代內建函式,獲得更高效能。
在AST到SSA轉換期間,為了方便複用,一些節點也降級為更簡單的元件。例如,內建拷貝(譯者猜:copy?)替換記憶體移動(譯者猜:memmove?)並將range重寫為for。由於歷史原因,其中一些在轉換為SSA之前完成,未來計劃全部移至這裡。
然後,一系列與機器無關的pass和規則被應用。這些不涉及任何單一計算機體系結構。
這些環節包括消除無用程式碼、刪除非必需的空值檢查以及刪除未使用的分支。通用重寫規則主要涉及表示式,例如用常量替換、乘法和浮點運算的優化。

4.生成機器程式碼

cmd/compile/internal/ssa(SSA低階化和特定架構處理)
cmd/internal/obj(機器碼生成)
機器依賴相關的階段以比較“較低”的操作開始,這些操作將通用變數重寫成其特定於機器的變體。例如,amd64架構中,可以合併許多載入儲存操作。
要注意”低階“的操作執行所有機器特定的重寫規則,它也應用了大量優化。
一旦SSA被“低階”處理並且更具體地針對目標體系架構,就要執行最終程式碼優化的處理步驟了。其中包含了另一個無用程式碼消除的步驟。該步驟會將變數移動到更靠近它們被使用的位置、刪除從未被讀取的區域性變數以及暫存器分配。
此步驟完成的其他重要工作包括堆疊佈局和指標分析。堆疊佈局將堆疊偏移分配給區域性變數,指標分析計算每個GC安全點上的堆疊指標是否仍然是活動的。

在SSA生成階段結束時,Go函式已轉換為sequence。這些sequence最終被轉換成機器碼。

相關文章