Go 編譯器介紹
在談到編譯器時,有時可能會聽到 前端 和 後端 這兩個術語。粗略地說,這些對應於我們將在此列出的前兩個和後兩個階段。第三個術語 中間端 通常指的是第二階段執行的大部分工作。
請注意,go/parser
和 go/types
等 go/*
系列的包與編譯器無關。由於編譯器最初是用 C 編寫的,所以這些 go/*
包被開發出來以便於能夠寫出和 Go
代碼一起工作的工具,例如 gofmt
和 vet
。
需要澄清的是,名稱 「gc」 代表 「 Go 編譯器 」,與大寫 GC 無關,後者代表 垃圾收集 。
1、解析
cmd/compile/internal/syntax
( 詞法分析器 、 解析器 、 語法樹 )
在編譯的第一階段,源代碼被標記化(詞法分析)、解析(語法分析),並為每個源文件構造語法樹(LCTT 譯註:這裡標記指 token,它是一組預定義的、能夠識別的字元串,通常由名字和值構成,其中名字一般是詞法的類別,如標識符、關鍵字、分隔符、操作符、文字和注釋等;語法樹,以及下文提到的 抽象語法樹 (AST),是指用樹來表達程序設計語言的語法結構,通常葉子節點是操作數,其它節點是操作碼)。
每個語法樹都是相應源文件的確切表示,其中節點對應於源文件的各種元素,例如表達式、聲明和語句。語法樹還包括位置信息,用於錯誤報告和創建調試信息。
2、類型檢查和 AST 變換
cmd/compile/internal/gc
(創建編譯器 AST, 類型檢查 , AST 變換 )
gc 包中包含一個繼承自(早期)C 語言實現的版本的 AST 定義。所有代碼都是基於它編寫的,所以 gc 包必須做的第一件事就是將 syntax 包(定義)的語法樹轉換為編譯器的 AST 表示法。這個額外步驟可能會在將來重構。
然後對 AST 進行類型檢查。第一步是名字解析和類型推斷,它們確定哪個對象屬於哪個標識符,以及每個表達式具有的類型。類型檢查包括特定的額外檢查,例如「聲明但未使用」以及確定函數是否會終止。
特定變換也基於 AST 完成。一些節點被基於類型信息而細化,例如把字元串加法從算術加法的節點類型中拆分出來。其它一些例子是 死代碼消除 , 函數調用內聯 和 逃逸分析 (LCTT 譯註:逃逸分析是一種分析指針有效範圍的方法)。
3、通用 SSA
cmd/compile/internal/gc
(轉換成 SSA)cmd/compile/internal/ssa
(SSA 相關的 環節 和規則)
(LCTT 譯註:許多常見高級語言的編譯器無法通過一次掃描源代碼或 AST 就完成所有編譯工作,取而代之的做法是多次掃描,每次完成一部分工作,並將輸出結果作為下次掃描的輸入,直到最終產生目標代碼。這裡每次掃描稱作一個 環節 ;最後一個環節之前所有的環節得到的結果都可稱作中間表示法,本文中 AST、SSA 等都屬於中間表示法。SSA,靜態單賦值形式,是中間表示法的一種性質,它要求每個變數只被賦值一次且在使用前被定義)。
在此階段,AST 將被轉換為 靜態單賦值 (SSA)形式,這是一種具有特定屬性的低級 中間表示法 ,可以更輕鬆地實現優化並最終從它生成機器碼。
在這個轉換過程中,將完成 內置函數 的處理。這些是特殊的函數,編譯器被告知逐個分析這些函數並決定是否用深度優化的代碼替換它們(LCTT 譯註:內置函數指由語言本身定義的函數,通常編譯器的處理方式是使用相應實現函數的指令序列代替對函數的調用指令,有點類似內聯函數)。
在 AST 轉化成 SSA 的過程中,特定節點也被低級化為更簡單的組件,以便於剩餘的編譯階段可以基於它們工作。例如,內建的拷貝被替換為內存移動,range
循環被改寫為 for
循環。由於歷史原因,目前這裡面有些在轉化到 SSA 之前發生,但長期計劃則是把它們都移到這裡(轉化 SSA)。
然後,一系列機器無關的規則和編譯環節會被執行。這些並不考慮特定計算機體系結構,因此對所有 GOARCH
變數的值都會運行。
這類通用的編譯環節的一些例子包括,死代碼消除、移除不必要的空值檢查,以及移除無用的分支等。通用改寫規則主要考慮表達式,例如將一些表達式替換為常量,優化乘法和浮點操作。
4、生成機器碼
cmd/compile/internal/ssa
(SSA 低級化和架構特定的環節)cmd/internal/obj
(機器碼生成)
編譯器中機器相關的階段開始於「低級」的編譯環節,該階段將通用變數改寫為它們的特定的機器碼形式。例如,在 amd64 架構中操作數可以在內存中操作,這樣許多 載入-存儲 操作就可以被合併。
注意低級的編譯環節運行所有機器特定的重寫規則,因此當前它也應用了大量優化。
一旦 SSA 被「低級化」並且更具體地針對目標體系結構,就要運行最終代碼優化的編譯環節了。這包含了另外一個死代碼消除的環節,它將變數移動到更靠近它們使用的地方,移除從來沒有被讀過的局部變數,以及 寄存器 分配。
本步驟中完成的其它重要工作包括 堆棧布局 ,它將堆棧偏移位置分配給局部變數,以及 指針活性分析 ,後者計算每個垃圾收集安全點上的哪些堆棧上的指針仍然是活動的。
在 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
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive