真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

golang的編譯過程-創(chuàng)新互聯(lián)

第一個(gè)helloworld程序

package main
import "fmt"
func main() {
    fmt.Println("Hello, world")
}
  • 在本文中,我們將介紹初學(xué)者比較關(guān)心的話題:go語言如何編譯為機(jī)器碼

    創(chuàng)新互聯(lián)公司云計(jì)算的互聯(lián)網(wǎng)服務(wù)提供商,擁有超過13年的服務(wù)器租用、四川雅安電信機(jī)房、云服務(wù)器、網(wǎng)絡(luò)空間、網(wǎng)站系統(tǒng)開發(fā)經(jīng)驗(yàn),已先后獲得國家工業(yè)和信息化部頒發(fā)的互聯(lián)網(wǎng)數(shù)據(jù)中心業(yè)務(wù)許可證。專業(yè)提供云主機(jī)、網(wǎng)絡(luò)空間、域名注冊、VPS主機(jī)、云服務(wù)器、香港云服務(wù)器、免備案服務(wù)器等。
  • 本文的目標(biāo)是希望讀者對go語言的編譯過程有一個(gè)全面的理解

  • 一段程序要運(yùn)行起來,需要將go代碼生成機(jī)器能夠識別的二進(jìn)制代碼

  • go代碼生成機(jī)器碼需要編譯器經(jīng)歷:
    詞法分析 => 語法分析 => 類型檢查 => 中間代碼 => 代碼優(yōu)化 => 生成機(jī)器碼

  • Go語言的編譯器入口是 src/cmd/compile/internal/gc 包中的 main.go 文件,此函數(shù)會(huì)先獲取命令行傳入的參數(shù)并更新編譯的選項(xiàng)和配置

  • 隨后就會(huì)開始運(yùn)行 parseFiles 函數(shù)對輸入的所有文件進(jìn)行詞法與語法分析

func Main(archInit func(*Arch)) {
    // ...

    lines := parseFiles(flag.Args())
  • 接下來我們將對各個(gè)階段做深入介紹

詞法分析

  • 所有的編譯過程都是從解析代碼的源文件開始的

  • 詞法分析的作用就是解析源代碼文件,它將文件中的字符串序列轉(zhuǎn)換成Token序列,方便后面的處理和解析

  • 我們一般會(huì)把執(zhí)行詞法分析的程序稱為詞法解析器(lexer)

  • Token可以是關(guān)鍵字,字符串,變量名,函數(shù)名

  • 有效程序的"單詞"都由Token表示,具體來說,這意味著"package","main","func" 等單詞都為Token

  • Go語言允許我們使用go/scanner和go/token包在Go程序中執(zhí)行解析程序,從而可以看到類似被編譯器解析后的結(jié)構(gòu)

  • 如果在語法解析的過程中發(fā)生了任何語法錯(cuò)誤,都會(huì)被語法解析器發(fā)現(xiàn)并將消息打印到標(biāo)準(zhǔn)輸出上,整個(gè)編譯過程也會(huì)隨著錯(cuò)誤的出現(xiàn)而被中止

  • helloworld程序解析后如下所示

1:1   package "package"
1:9   IDENT   "main"
1:13  ;       "\n"
2:1   import  "import"
2:8   STRING  "\"fmt\""
2:13  ;       "\n"
3:1   func    "func"
3:6   IDENT   "main"
3:10  (       ""
3:11  )       ""
3:13  {       ""
4:3   IDENT   "fmt"
4:6   .       ""
4:7   IDENT   "Println"
4:14  (       ""
4:15  STRING  "\"Hello, world!\""
4:30  )       ""
4:31  ;       "\n"
5:1   }       ""
5:2   ;       "\n"
5:3   EOF     ""
  • 我們可以看到,詞法解析器添加了分號,分號常常是在C語言等語言中一條語句后添加的

  • 這解釋了為什么Go不需要分號:詞法解析器可以智能地加入分號

語法分析

  • 語法分析的輸入就是詞法分析器輸出的 Token 序列,這些序列會(huì)按照順序被語法分析器進(jìn)行解析,語法的解析過程就是將詞法分析生成的 Token 按照語言定義好的文法(Grammar)自下而上或者自上而下的進(jìn)行規(guī)約,每一個(gè) Go 的源代碼文件最終會(huì)被歸納成一個(gè) SourceFile 結(jié)構(gòu):

SourceFile = PackageClause ";" { ImportDecl ";" } { TopLevelDecl ";" }
  • 標(biāo)準(zhǔn)的 Golang 語法解析器使用的就是 LALR(1) 的文法,語法解析的結(jié)果生成了抽象語法樹(Abstract Syntax Tree,AST)

  • 抽象語法樹(Abstract Syntax Tree,AST),或簡稱語法樹(Syntax tree),是源代碼語法結(jié)構(gòu)的一種抽象表示。它以樹狀的形式表現(xiàn)編程語言的語法結(jié)構(gòu),樹上的每個(gè)節(jié)點(diǎn)都表示源代碼中的一種結(jié)構(gòu)。

  • 之所以說語法是“抽象”的,是因?yàn)檫@里的語法并不會(huì)表示出真實(shí)語法中出現(xiàn)的每個(gè)細(xì)節(jié)。比如,嵌套括號被隱含在樹的結(jié)構(gòu)中,并沒有以節(jié)點(diǎn)的形式呈現(xiàn);而類似于 if-condition-then 這樣的條件跳轉(zhuǎn)語句,可以使用帶有三個(gè)分支的節(jié)點(diǎn)來表示。

  • 與AST相對應(yīng)的是CST(Concrete Syntax Trees),讀者可以在參考資料中拓展閱讀二者的差別

  • 在AST中,我們能夠看到程序結(jié)構(gòu),例如函數(shù)和常量聲明

  • Go為我們提供了用于解析程序和查看AST的軟件包:go/parser 和 go/ast

  • helloworld程序生成的AST如下所示

     0  *ast.File {
     1  .  Package: 1:1
     2  .  Name: *ast.Ident {
     3  .  .  NamePos: 1:9
     4  .  .  Name: "main"
     5  .  }
     6  .  Decls: []ast.Decl (len = 2) {
     7  .  .  0: *ast.GenDecl {
     8  .  .  .  TokPos: 3:1
     9  .  .  .  Tok: import
    10  .  .  .  Lparen: -
    11  .  .  .  Specs: []ast.Spec (len = 1) {
    12  .  .  .  .  0: *ast.ImportSpec {
    13  .  .  .  .  .  Path: *ast.BasicLit {
    14  .  .  .  .  .  .  ValuePos: 3:8
    15  .  .  .  .  .  .  Kind: STRING
    16  .  .  .  .  .  .  Value: "\"fmt\""
    17  .  .  .  .  .  }
    18  .  .  .  .  .  EndPos: -
    19  .  .  .  .  }
    20  .  .  .  }
    21  .  .  .  Rparen: -
    22  .  .  }
    23  .  .  1: *ast.FuncDecl {
    24  .  .  .  Name: *ast.Ident {
    25  .  .  .  .  NamePos: 5:6
    26  .  .  .  .  Name: "main"
    27  .  .  .  .  Obj: *ast.Object {
    28  .  .  .  .  .  Kind: func
    29  .  .  .  .  .  Name: "main"
    30  .  .  .  .  .  Decl: *(obj @ 23)
    31  .  .  .  .  }
    32  .  .  .  }
    33  .  .  .  Type: *ast.FuncType {
    34  .  .  .  .  Func: 5:1
    35  .  .  .  .  Params: *ast.FieldList {
    36  .  .  .  .  .  Opening: 5:10
    37  .  .  .  .  .  Closing: 5:11
    38  .  .  .  .  }
    39  .  .  .  }
    40  .  .  .  Body: *ast.BlockStmt {
    41  .  .  .  .  Lbrace: 5:13
    42  .  .  .  .  List: []ast.Stmt (len = 1) {
    43  .  .  .  .  .  0: *ast.ExprStmt {
    44  .  .  .  .  .  .  X: *ast.CallExpr {
    45  .  .  .  .  .  .  .  Fun: *ast.SelectorExpr {
    46  .  .  .  .  .  .  .  .  X: *ast.Ident {
    47  .  .  .  .  .  .  .  .  .  NamePos: 6:2
    48  .  .  .  .  .  .  .  .  .  Name: "fmt"
    49  .  .  .  .  .  .  .  .  }
    50  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
    51  .  .  .  .  .  .  .  .  .  NamePos: 6:6
    52  .  .  .  .  .  .  .  .  .  Name: "Println"
    53  .  .  .  .  .  .  .  .  }
    54  .  .  .  .  .  .  .  }
    55  .  .  .  .  .  .  .  Lparen: 6:13
    56  .  .  .  .  .  .  .  Args: []ast.Expr (len = 1) {
    57  .  .  .  .  .  .  .  .  0: *ast.BasicLit {
    58  .  .  .  .  .  .  .  .  .  ValuePos: 6:14
    59  .  .  .  .  .  .  .  .  .  Kind: STRING
    60  .  .  .  .  .  .  .  .  .  Value: "\"Hello, world!\""
    61  .  .  .  .  .  .  .  .  }
    62  .  .  .  .  .  .  .  }
    63  .  .  .  .  .  .  .  Ellipsis: -
    64  .  .  .  .  .  .  .  Rparen: 6:29
    65  .  .  .  .  .  .  }
    66  .  .  .  .  .  }
    67  .  .  .  .  }
    68  .  .  .  .  Rbrace: 7:1
    69  .  .  .  }
    70  .  .  }
    71  .  }
    ..  .  .. // Left out for brevity
    83  }
  • 如上的輸出中我們能夠看出一些信息

  • 在Decls字段中,包含文件中所有聲明的列表,例如import,常量,變量和函數(shù)

  • 為了進(jìn)一步理解,我們看一下對其的圖形化抽象表示

golang的編譯過程

  • 紅色表示與節(jié)點(diǎn)相對應(yīng)的代碼

  • main函數(shù)包含了3個(gè)部分,名稱, 聲明, 主體

  • 名稱是單詞main的標(biāo)識

  • 由Type字段指定的聲明將包含參數(shù)列表和返回類型

  • 主體由一系列語句組成,其中包含程序的所有行。在本例中,只有一行

  • fmt.Println語句由AST中的很多部分組成,由ExprStmt聲明。

  • ExprStmt代表一個(gè)表達(dá)式,其可以是本例中的函數(shù)調(diào)用,也可以是二進(jìn)制運(yùn)算(例如加法和減法等)

  • 我們的ExprStmt包含一個(gè)CallExpr,這是我們的實(shí)際函數(shù)調(diào)用。這又包括幾個(gè)部分,其中最重要的是FunArgs

  • Fun包含對函數(shù)調(diào)用的引用,由SelectorExpr聲明。在AST中,編譯器尚未知道fmt是一個(gè)程序包,它也可能是AST中的變量

  • Args包含一個(gè)表達(dá)式列表,這些表達(dá)式是該函數(shù)的參數(shù)。在本例中,我們已將文字字符串傳遞給函數(shù),因此它由類型為STRINGBasicLit表示。

類型檢查

  • 構(gòu)造AST之后,將會(huì)對所有import的包進(jìn)行解析

  • 接著Go語言的編譯器會(huì)對語法樹中定義和使用的類型進(jìn)行檢查,類型檢查分別會(huì)按照順序?qū)Σ煌愋偷墓?jié)點(diǎn)進(jìn)行驗(yàn)證,按照以下的順序進(jìn)行處理:

    • 常量、類型和函數(shù)名及類型

    • 變量的賦值和初始化

    • 函數(shù)和閉包的主體

    • 哈希鍵值對的類型

    • 導(dǎo)入函數(shù)體

    • 外部的聲明

  • 通過對每一棵抽象節(jié)點(diǎn)樹的遍歷,我們在每一個(gè)節(jié)點(diǎn)上都會(huì)對當(dāng)前子樹的類型進(jìn)行驗(yàn)證保證當(dāng)前節(jié)點(diǎn)上不會(huì)出現(xiàn)類型錯(cuò)誤的問題,所有的類型錯(cuò)誤和不匹配都會(huì)在這一個(gè)階段被發(fā)現(xiàn)和暴露出來。

  • 類型檢查的階段不止會(huì)對樹狀結(jié)構(gòu)的節(jié)點(diǎn)進(jìn)行驗(yàn)證,同時(shí)也會(huì)對一些內(nèi)建的函數(shù)進(jìn)行展開和改寫,例如 make 關(guān)鍵字在這個(gè)階段會(huì)根據(jù)子樹的結(jié)構(gòu)被替換成 makeslice 或者 makechan 等函數(shù)。

  • 類型檢查不止對類型進(jìn)行了驗(yàn)證工作,還對 AST 進(jìn)行了改寫以及處理Go語言內(nèi)置的關(guān)鍵字

生成中間代碼

  • 在上面的步驟完成之后,可以明確代碼是正確有效的

  • 接著將AST轉(zhuǎn)換為程序的低級表示形式,即靜態(tài)單一賦值形式(Static Single Assignment Form,SSA)形式,核心代碼位于gc/ssa.go

  • SSA不是程序的最終狀態(tài),其可以更輕松地應(yīng)用優(yōu)化,其中最重要的是始終在使用變量之前定義變量,并且每個(gè)變量只分配一次

  • 例如下面的代碼我們可以看到第一個(gè)x的賦值沒有必要的

x = 1
x = 2
y = 7
  • 編輯器會(huì)將上面的代碼變?yōu)槿缦?,從而?huì)刪除x_1

x_1 = 1
x_2 = 2
y_1 = 7
  • 生成SSA的初始版本后,將應(yīng)用許多優(yōu)化過程。這些優(yōu)化應(yīng)用于某些代碼段,這些代碼段可以使處理器執(zhí)行起來更簡單或更快速。

  • 例如下面的代碼是永遠(yuǎn)不會(huì)執(zhí)行的,因此可以被消除。

 if (false) {
     fmt.Println(“test”)
 }
  • 優(yōu)化的另一個(gè)示例是可以刪除某些nil檢查,因?yàn)榫幾g器可以證明這些檢查永遠(yuǎn)不會(huì)出錯(cuò)

  • 在對SSA進(jìn)行優(yōu)化的過程中使用了S表達(dá)式(S-expressions)進(jìn)行描述, S-expressions 是嵌套列表(樹形結(jié)構(gòu))數(shù)據(jù)的一種表示法,由編程語言Lisp發(fā)明并普及

  • SSA優(yōu)化過程中對于S表達(dá)式的應(yīng)用如下所示,將8位的常量乘法組合起來

(Mul8 (Const8 [c]) (Const8 [d])) -> (Const8 [int64(int8(c*d))])
  • 具體的優(yōu)化包括

    • 常數(shù)傳播(constant propagation)

    • 值域傳播(value range propagation)

    • 稀疏有條件的常數(shù)傳播(sparse conditional constant propagation)

    • 消除無用的程式碼(dead code elimination)

    • 全域數(shù)值編號(global value numbering)

    • 消除部分的冗余(partial redundancy elimination)

    • 強(qiáng)度折減(strength reduction)

    • 寄存器分配(register allocation)

SSA優(yōu)化

  • 我們可以用下面的簡單代碼來查看SSA及其優(yōu)化過程

  • 對于如下程序

package main

import "fmt"

func main() {
  fmt.Println(2)
}
  • 我們需要在命令行運(yùn)行如下指令來查看SSA

  • GOSSAFUNC環(huán)境變量代表我們需要查看SSA的函數(shù)并創(chuàng)建ssa.html文件

  • GOOS、GOARCH代表編譯為在Linux 64-bit平臺(tái)運(yùn)行的代碼

  • go build用-ldflags給go編譯器傳入?yún)?shù)

  • -S 標(biāo)識將打印匯編代碼

$ GOSSAFUNC=main GOOS=linux GOARCH=amd64 go build -gcflags "-S" simple.go
  • 下面的命令等價(jià)

GOSSAFUNC=main GOOS=linux GOARCH=amd64 go tool compile main.go
  • 當(dāng)打開ssa.html時(shí),將顯示許多代碼片段,其中一些片段是隱藏的。

golang的編譯過程

  • Start片段是從AST生成的SSA。genssa片段是最終生成的Plan9匯編代碼

  • Start片段如下

start
b1:-
v1 (?) = InitMem 
v2 (?) = SP 
v3 (?) = SB 
v4 (?) = Addr <*uint8> {type.int} v3
v5 (?) = Addr <*int> {""..stmp_0} v3
v6 (6) = IMake  v4 v5 (~arg0[interface {}])
v7 (?) = ConstInterface 
v8 (?) = ArrayMake1 <[1]interface {}> v7
v9 (6) = VarDef  {.autotmp_11} v1
v10 (6) = LocalAddr <*[1]interface {}> {.autotmp_11} v2 v9
v11 (6) = Store  {[1]interface {}} v10 v8 v9
v12 (6) = LocalAddr <*[1]interface {}> {.autotmp_11} v2 v11
v13 (6) = NilCheck  v12 v11
v14 (?) = Const64  [0] (fmt..autotmp_3[int], fmt.n[int])
v15 (?) = Const64  [1]
v16 (6) = PtrIndex <*interface {}> v12 v14
v17 (6) = Store  {interface {}} v16 v6 v11
v18 (6) = NilCheck  v12 v17
v19 (6) = Copy <*interface {}> v12
v20 (6) = IsSliceInBounds  v14 v15
v25 (?) = ConstInterface  (fmt..autotmp_4[error], fmt.err[error])
v28 (?) = OffPtr <*io.Writer> [0] v2
v29 (?) = Addr <*uint8> {go.itab.*os.File,io.Writer} v3
v30 (?) = Addr <**os.File> {os.Stdout} v3
v34 (?) = OffPtr <*[]interface {}> [16] v2
v37 (?) = OffPtr <*int> [40] v2
v39 (?) = OffPtr <*error> [48] v2
If v20 → b2 b3 (likely) (6)
b2: ← b1-
v23 (6) = Sub64  v15 v14
v24 (6) = SliceMake <[]interface {}> v19 v23 v23 (fmt.a[[]interface {}])
v26 (6) = Copy  v17
v27 (+6) = InlMark  [0] v26
v31 (274) = Load <*os.File> v30 v26
v32 (274) = IMake  v29 v31
v33 (274) = Store  {io.Writer} v28 v32 v26
v35 (274) = Store  {[]interface {}} v34 v24 v33
v36 (274) = StaticCall  {fmt.Fprintln} [64] v35
v38 (274) = Load  v37 v36 (fmt.n[int], fmt..autotmp_3[int])
v40 (274) = Load  v39 v36 (fmt.err[error], fmt..autotmp_4[error])
Plain → b4 (+6)
b3: ← b1-
v21 (6) = Copy  v17
v22 (6) = PanicBounds  [6] v14 v15 v21
Exit v22 (6)
b4: ← b2-
v41 (7) = Copy  v36
Ret v41
name ~arg0[interface {}]: v6
name fmt.a[[]interface {}]: v24
name fmt.n[int]: v14 v38
name fmt.err[error]: v25 v40
name fmt..autotmp_3[int]: v14 v38
name fmt..autotmp_4[error]: v25 v40
  • 每個(gè)v是一個(gè)新變量,可以單擊以查看使用它的位置。

  • b是代碼塊,本例中我們有3個(gè)代碼塊:b1, b2和 b3

  • b1將始終被執(zhí)行,b2和b3是條件塊,如b1最后一行所示:If v20 → b2 b3 (likely) (6),只有v20為true會(huì)執(zhí)行b2,v20為false會(huì)執(zhí)行b3

  • 我們可以點(diǎn)擊v20查看其定義,其定義是v20 (6) = IsSliceInBounds v14 v15

  • IsSliceInBounds 會(huì)執(zhí)行如下檢查:0 <= v14 <= v15 是否成立

  • 我們可以單擊v14和v15來查看它們的定義:v14 = Const64 [0] ,v15 = Const64 [1]

  • Const64為64位常量,因此 0 <= 0 <= 1 始終成立,因此v20始終成立

  • 當(dāng)我們在opt片段查看v20時(shí),會(huì)發(fā)現(xiàn)v20 (6) = ConstBool [true],v20變?yōu)榱耸冀K為true

  • 因此,我們會(huì)看到在opt deadcode片段中,b3塊被刪除了

代碼優(yōu)化

  • 生成SSA之后,Go編譯器還會(huì)進(jìn)行一系列簡單的優(yōu)化,例如無效和無用代碼的刪除

  • 我們將用同樣的ssa.html文件,比較lower 和 lowered deadcode片段

golang的編譯過程

  • 在HTML文件中,某些行顯示為灰色,這意味著它們將在下一階段之一中被刪除或更改

  • 例如v15 = MOVQconst [1]為灰色,因?yàn)槠湓诤竺娓緵]有被使用。MOVQconst與我們之前看到的指令Const64相同,僅適用于amd64平臺(tái)

機(jī)器碼生成

  • 完成以上步驟,最終還會(huì)生成跨平臺(tái)的plan9匯編指令,并進(jìn)一步根據(jù)目標(biāo)的 CPU 架構(gòu)生成二進(jìn)制機(jī)器代碼

  • Go語言源代碼的 cmd/compile/internal 目錄中包含了非常多機(jī)器碼生成相關(guān)的包

  • 不同類型的 CPU 分別使用了不同的包進(jìn)行生成 amd64、arm、arm64、mips、mips64、ppc64、s390x、x86 和 wasm

  • Go語言能夠在幾乎全部常見的 CPU 指令集類型上運(yùn)行。

另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)cdcxhl.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。


標(biāo)題名稱:golang的編譯過程-創(chuàng)新互聯(lián)
文章路徑:http://weahome.cn/article/gijcd.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部