Go 編譯器介紹
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 cmd/compile/internal/ssa
(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 cmd/internal/obj
編譯器中機器相關的階段開始於“低階”的編譯環節,該階段將通用變數改寫為它們的特定的機器碼形式。例如,在 amd64 架構中運算元可以在記憶體中操作,這樣許多載入-儲存load-store操作就可以被合併。
注意低階的編譯環節執行所有機器特定的重寫規則,因此當前它也應用了大量優化。
一旦 SSA 被“低階化”並且更具體地針對目標體系結構,就要執行最終程式碼優化的編譯環節了。這包含了另外一個死程式碼消除的環節,它將變數移動到更靠近它們使用的地方,移除從來沒有被讀過的區域性變數,以及暫存器register分配。
本步驟中完成的其它重要工作包括堆疊佈局stack frame layout,它將堆疊偏移位置分配給區域性變數,以及指標活性分析pointer liveness analysis,後者計算每個垃圾收集安全點上的哪些堆疊上的指標仍然是活動的。
在 SSA 生成階段結束時,Go 函式已被轉換為一系列 obj.Prog
指令。它們被傳遞給彙編程式( cmd/internal/obj
),後者將它們轉換為機器碼並輸出最終的目標檔案。目標檔案還將包含反射資料,匯出資料和除錯資訊。
擴充套件閱讀
要深入瞭解 SSA 包的工作方式,包括它的環節和規則,請轉到 ofollow,noindex" target="_blank">cmd/compile/internal/ssa/README.md 。
via: https://github.com/golang/go/blob/master/src/cmd/compile/README.md
作者: mvdan 譯者: stephenxs 校對: pityonline , wxy
Linux公社的RSS地址: https://www.linuxidc.com/rssFeed.aspx
本文永久更新連結地址: https://www.linuxidc.com/Linux/2018-09/154104.htm