cmd/compile
包含構成 Go 編譯器主要的包。編譯器在邏輯上可以被分為四個階段,我們將簡要介紹這幾個階段以及包含相應代碼的包的列表。
在談到編譯器時,有時可能會聽到前端front-end和後端back-end這兩個術語。粗略地說,這些對應於我們將在此列出的前兩個和後兩個階段。第三個術語中間端middle-end通常指的是第二階段執行的大部分工作。
請注意,go/parser
和 go/types
等 go/*
系列的包與編譯器無關。由於編譯器最初是用 C 編寫的,所以這些 go/*
包被開發出來以便於能夠寫出和 Go
代碼一起工作的工具,例如 gofmt
和 vet
。
需要澄清的是,名稱 “gc” 代表 “Go 編譯器Go compiler”,與大寫 GC 無關,後者代表垃圾收集garbage collection。
1、解析
cmd/compile/internal/syntax
(詞法分析器lexer、解析器parser、文法樹syntax tree)
在編譯的第一階段,原始碼被標記化(詞法分析)、解析(文法分析),並為每個源檔案構造文法樹(LCTT 譯註:這裡標記指 token,它是一組預定義的、能夠識別的字串,通常由名字和值構成,其中名字一般是詞法的類別,如標識符、關鍵字、分隔字元、操作符、文字和注釋等;文法樹,以及下文提到的抽象文法樹Abstract Syntax Tree(AST),是指用樹來表達程式設計語言的文法結構,通常葉子節點是運算元,其它節點是作業碼)。
每個文法樹都是相應源檔案的確切表示,其中節點對應於源檔案的各種元素,例如運算式、聲明和語句。文法樹還包括位置資訊,用於錯誤報表和建立調試資訊。
2、類型檢查和 AST 變換
cmd/compile/internal/gc
(建立編譯器 AST,類型檢查type-checking,AST 變換AST transformation)
gc 包中包含一個繼承自(早期)C 語言實現的版本的 AST 定義。所有代碼都是基於它編寫的,所以 gc 包必須做的第一件事就是將 syntax 包(定義)的文法樹轉換為編譯器的 AST 標記法。這個額外步驟可能會在將來重構。
然後對 AST 進行類型檢查。第一步是名字解析和類型推斷,它們確定哪個對象屬於哪個標識符,以及每個運算式具有的類型。類型檢查包括特定的額外檢查,例如“聲明但未使用”以及確定函數是否會終止。
特定變換也基於 AST 完成。一些節點被基於類型資訊而細化,例如把字串加法從算術加法的節點類型中拆分出來。其它一些例子是無作用程式碼消除dead code elimination,函數調用內聯function call inlining和逃逸分析escape analysis(LCTT 譯註:逃逸分析是一種分析指標有效範圍的方法)。
3、通用 SSA
cmd/compile/internal/gc
(轉換成 SSA)
cmd/compile/internal/ssa
(SSA 相關的環節pass和規則)
(LCTT 譯註:許多常見進階語言的編譯器無法通過一次掃描原始碼或 AST 就完成所有編譯工作,取而代之的做法是多次掃描,每次完成一部分工作,並將輸出結果作為下次掃描的輸入,直到最終產生目標代碼。這裡每次掃描稱作一個環節pass;最後一個環節之前所有的環節得到的結果都可稱作中間標記法,本文中 AST、SSA 等都屬於中間標記法。SSA,靜態單賦值形式,是中間標記法的一種性質,它要求每個變數只被賦值一次且在使用前被定義)。
在此階段,AST 將被轉換為靜態單賦值Static Single Assignment(SSA)形式,這是一種具有特定屬性的低級中間標記法intermediate representation,可以更輕鬆地實現最佳化並最終從它產生機器碼。
在這個轉換過程中,將完成內建函數function intrinsics的處理。這些是特殊的函數,編譯器被告知逐個分析這些函數並決定是否用深度最佳化的代碼替換它們(LCTT 譯註:內建函數指由語言本身定義的函數,通常編譯器的處理方式是使用相應實現函數的指令序列代替對函數的調用指令,有點類似內嵌函式)。
在 AST 轉化成 SSA 的過程中,特定節點也被低級化為更簡單的組件,以便於剩餘的編譯階段可以基於它們工作。例如,內建的拷貝被替換為記憶體移動,range
迴圈被改寫為 for
迴圈。由於曆史原因,目前這裡面有些在轉化到 SSA 之前發生,但長期計劃則是把它們都移到這裡(轉化 SSA)。
然後,一系列機器無關的規則和編譯環節會被執行。這些並不考慮特定電腦體繫結構,因此對所有 GOARCH
變數的值都會運行。
這類通用的編譯環節的一些例子包括,無作用程式碼消除、移除不必要的空值檢查,以及移除無用的分支等。通用改寫規則主要考慮運算式,例如將一些運算式替換為常量,最佳化乘法和浮點操作。
4、產生機器碼
cmd/compile/internal/ssa
(SSA 低級化和架構特定的環節)
cmd/internal/obj
(機器碼產生)
編譯器中機器相關的階段開始於“低級”的編譯環節,該階段將通用變數改寫為它們的特定的機器碼形式。例如,在 amd64 架構中運算元可以在記憶體中操作,這樣許多載入-儲存load-store操作就可以被合并。
注意低級的編譯環節運行所有機器特定的重寫規則,因此當前它也應用了大量最佳化。
一旦 SSA 被“低級化”並且更具體地針對目標體繫結構,就要運行最終代碼最佳化的編譯環節了。這包含了另外一個無作用程式碼消除的環節,它將變數移動到更靠近它們使用的地方,移除從來沒有被讀過的局部變數,以及寄存器register分配。
本步驟中完成的其它重要工作包括堆棧布局stack frame layout,它將堆棧位移位置分配給局部變數,以及指標活性分析pointer liveness analysis,後者計算每個垃圾收集安全點上的哪些堆棧上的指標仍然是活動的。
在 SSA 產生階段結束時,Go 函數已被轉換為一系列 obj.Prog
指令。它們被傳遞給組譯工具(cmd/internal/obj
),後者將它們轉換為機器碼並輸出最終的目標檔案。目標檔案還將包含反射資料,匯出資料和調試資訊。
擴充閱讀
要深入瞭解 SSA 包的工作方式,包括它的環節和規則,請轉到 cmd/compile/internal/ssa/README.md。
via: https://github.com/golang/go/blob/master/src/cmd/compile/README.md
作者:mvdan 譯者:stephenxs 校對:pityonline, wxy
本文由 LCTT 原創編譯,Linux中國 榮譽推出