日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

cmd编译可以通过执行没有结果_Go语言是如何完成编译的

發(fā)布時(shí)間:2023/12/3 编程问答 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 cmd编译可以通过执行没有结果_Go语言是如何完成编译的 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

Go語(yǔ)言是一門需要編譯才能運(yùn)行的編程語(yǔ)言,也就說代碼在運(yùn)行之前需要通過編譯器生成二進(jìn)制機(jī)器碼,隨后二進(jìn)制文件才能在目標(biāo)機(jī)器上運(yùn)行,如果我們想要了解Go語(yǔ)言的實(shí)現(xiàn)原理,理解它的編譯過程就是一個(gè)沒有辦法繞過的事情。

預(yù)備知識(shí)

想要深入了解Go語(yǔ)言的編譯過程,需要提前了解一下編譯過程中涉及的一些術(shù)語(yǔ)和專業(yè)知識(shí)。這些知識(shí)其實(shí)在我們的日常工作和學(xué)習(xí)中比較難用到,但是對(duì)于理解編譯的過程和原理還是非常重要的。

1) 抽象語(yǔ)法樹

抽象語(yǔ)法樹(AST)是源代碼語(yǔ)法的結(jié)構(gòu)的一種抽象表示,它用樹狀的方式表示編程語(yǔ)言的語(yǔ)法結(jié)構(gòu)。抽象語(yǔ)法樹中的每一個(gè)節(jié)點(diǎn)都表示源代碼中的一個(gè)元素,每一顆子樹都表示一個(gè)語(yǔ)法元素,例如一個(gè) if else 語(yǔ)句,我們可以從 2 * 3 + 7 這一表達(dá)式中解析出下圖所示的抽象語(yǔ)法樹。

抽象語(yǔ)法樹


作為編譯器常用的數(shù)據(jù)結(jié)構(gòu),抽象語(yǔ)法樹抹去了源代碼中不重要的一些字符,比如空格、分號(hào)或者括號(hào)等等。編譯器在執(zhí)行完語(yǔ)法分析之后會(huì)輸出一個(gè)抽象語(yǔ)法樹,這棵樹會(huì)輔助編譯器進(jìn)行語(yǔ)義分析,我們可以用它來確定結(jié)構(gòu)正確的程序是否存在一些類型不匹配或不一致的問題。

2) 靜態(tài)單賦值

靜態(tài)單賦值(SSA)是中間代碼的一個(gè)特性,如果一個(gè)中間代碼具有靜態(tài)單賦值的特性,那么每個(gè)變量就只會(huì)被賦值一次,在實(shí)踐中我們通常會(huì)用添加下標(biāo)的方式實(shí)現(xiàn)每個(gè)變量只能被賦值一次的特性,這里以下面的代碼舉一個(gè)簡(jiǎn)單的例子:

x := 1x := 2y := x

根據(jù)分析,我們其實(shí)能夠發(fā)現(xiàn)上述的代碼其實(shí)并不需要第一個(gè)將 1 賦值給 x 的表達(dá)式,也就是這一表達(dá)式在整個(gè)代碼片段中是沒有作用的:

x1 := 1x2 := 2y1 := x2

從使用 SSA 的中間代碼我們就可以非常清晰地看出變量 y1 的值和 x1 是完全沒有任何關(guān)系的,所以在機(jī)器碼生成時(shí)其實(shí)就可以省略第一步,這樣就能減少需要執(zhí)行的指令來優(yōu)化這一段代碼。
根據(jù) Wikipedia(維基百科)對(duì) SSA 的介紹來看,在中間代碼中使用 SSA 的特性能夠?yàn)檎麄€(gè)程序?qū)崿F(xiàn)以下的優(yōu)化:

  • 常數(shù)傳播(constant propagation)
  • 值域傳播(value range propagation)
  • 稀疏有條件的常數(shù)傳播(sparse conditional constant propagation)
  • 消除無用的程式碼(dead code elimination)
  • 全域數(shù)值編號(hào)(global value numbering)
  • 消除部分的冗余(partial redundancy elimination)
  • 強(qiáng)度折減(strength reduction)
  • 寄存器分配(register allocation)

從 SSA 的作用我們就能看出,因?yàn)樗闹饕饔镁褪谴a的優(yōu)化,所以是編譯器后端(主要負(fù)責(zé)目標(biāo)代碼的優(yōu)化和生成)的一部分;當(dāng)然,除了 SSA 之外代碼編譯領(lǐng)域還有非常多的中間代碼優(yōu)化方法,優(yōu)化編譯器生成的代碼是一個(gè)非常古老并且復(fù)雜的領(lǐng)域,這里就不展開介紹了。

3) 指令集架構(gòu)

最后要介紹的一個(gè)預(yù)備知識(shí)就是指令集的架構(gòu)了,很多開發(fā)者都會(huì)遇到在生產(chǎn)環(huán)境運(yùn)行的結(jié)果和本地不同的問題,導(dǎo)致這種情況的原因其實(shí)非常復(fù)雜,不同機(jī)器使用不同的指令就是可能的原因之一。
我們大多數(shù)開發(fā)者都會(huì)使用 x86_64 的 Macbook 作為工作上主要使用的硬件,在命令行中輸入 uname -m 就能夠獲得當(dāng)前機(jī)器上硬件的信息:

$ uname -mx86_64

x86_64 是目前比較常見的指令集架構(gòu)之一,除了 x86_64 之外,還有其他類型的指令集架構(gòu),例如 amd64、arm64 以及 mips 等等,不同的處理器使用了大不相同的機(jī)器語(yǔ)言,所以很多編程語(yǔ)言為了在不同的機(jī)器上運(yùn)行需要將源代碼根據(jù)架構(gòu)翻譯成不同的機(jī)器代碼。
復(fù)雜指令集計(jì)算機(jī)(CISC)和精簡(jiǎn)指令集計(jì)算機(jī)(RISC)是目前的兩種 CPU 區(qū)別,它們的在設(shè)計(jì)理念上會(huì)有一些不同,從名字我們就能看出來這兩種不同的設(shè)計(jì)有什么區(qū)別,復(fù)雜指令集通過增加指令的數(shù)量減少需要執(zhí)行的質(zhì)量數(shù),而精簡(jiǎn)指令集能使用更少的指令完成目標(biāo)的計(jì)算任務(wù)。早期的 CPU 為了減少機(jī)器語(yǔ)言指令的數(shù)量使用復(fù)雜指令集完成計(jì)算任務(wù),這兩者之前的區(qū)別其實(shí)就是設(shè)計(jì)上的權(quán)衡。

編譯原理

Go語(yǔ)言編譯器的源代碼在 cmd/compile 目錄中,目錄下的文件共同構(gòu)成了Go語(yǔ)言的編譯器,學(xué)過編譯原理的人可能聽說過編譯器的前端和后端,編譯器的前端一般承擔(dān)著詞法分析、語(yǔ)法分析、類型檢查和中間代碼生成幾部分工作,而編譯器后端主要負(fù)責(zé)目標(biāo)代碼的生成和優(yōu)化,也就是將中間代碼翻譯成目標(biāo)機(jī)器能夠運(yùn)行的機(jī)器碼。


Go的編譯器在邏輯上可以被分成四個(gè)階段:詞法與語(yǔ)法分析、類型檢查和 AST 轉(zhuǎn)換、通用 SSA 生成和最后的機(jī)器代碼生成,下面我們來分別介紹一下這四個(gè)階段做的工作。

1) 詞法與語(yǔ)法分析

所有的編譯過程其實(shí)都是從解析代碼的源文件開始的,詞法分析的作用就是解析源代碼文件,它將文件中的字符串序列轉(zhuǎn)換成 Token 序列,方便后面的處理和解析,我們一般會(huì)把執(zhí)行詞法分析的程序稱為詞法解析器(lexer)。
而語(yǔ)法分析的輸入就是詞法分析器輸出的 Token 序列,這些序列會(huì)按照順序被語(yǔ)法分析器進(jìn)行解析,語(yǔ)法的解析過程就是將詞法分析生成的 Token 按照語(yǔ)言定義好的文法(Grammar)自下而上或者自上而下的進(jìn)行規(guī)約,每一個(gè) Go 的源代碼文件最終會(huì)被歸納成一個(gè) SourceFile 結(jié)構(gòu):

SourceFile = PackageClause ";" { ImportDecl ";" } { TopLevelDecl ";" }

標(biāo)準(zhǔn)的Go 語(yǔ)法解析器使用的就是 LALR(1) 的文法,語(yǔ)法解析的結(jié)果其實(shí)就是上面介紹過的抽象語(yǔ)法樹(AST),每一個(gè) AST 都對(duì)應(yīng)著一個(gè)單獨(dú)的Go語(yǔ)言文件,這個(gè)抽象語(yǔ)法樹中包括當(dāng)前文件屬于的包名、定義的常量、結(jié)構(gòu)體和函數(shù)等。


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

2) 類型檢查

當(dāng)拿到一組文件的抽象語(yǔ)法樹 AST 之后,Go語(yǔ)言的編譯器會(huì)對(duì)語(yǔ)法樹中定義和使用的類型進(jìn)行檢查,類型檢查分別會(huì)按照順序?qū)Σ煌愋偷墓?jié)點(diǎn)進(jìn)行驗(yàn)證,按照以下的順序進(jìn)行處理:

  • 常量、類型和函數(shù)名及類型;
  • 變量的賦值和初始化;
  • 函數(shù)和閉包的主體;
  • 哈希鍵值對(duì)的類型;
  • 導(dǎo)入函數(shù)體;
  • 外部的聲明;


通過對(duì)每一棵抽象節(jié)點(diǎn)樹的遍歷,我們?cè)诿恳粋€(gè)節(jié)點(diǎn)上都會(huì)對(duì)當(dāng)前子樹的類型進(jìn)行驗(yàn)證保證當(dāng)前節(jié)點(diǎn)上不會(huì)出現(xiàn)類型錯(cuò)誤的問題,所有的類型錯(cuò)誤和不匹配都會(huì)在這一個(gè)階段被發(fā)現(xiàn)和暴露出來。
類型檢查的階段不止會(huì)對(duì)樹狀結(jié)構(gòu)的節(jié)點(diǎn)進(jìn)行驗(yàn)證,同時(shí)也會(huì)對(duì)一些內(nèi)建的函數(shù)進(jìn)行展開和改寫,例如 make 關(guān)鍵字在這個(gè)階段會(huì)根據(jù)子樹的結(jié)構(gòu)被替換成 makeslice 或者 makechan 等函數(shù)。

我們其實(shí)能夠看出類型檢查不止做了驗(yàn)證類型的工作,還對(duì) AST 進(jìn)行了改寫和處理Go語(yǔ)言內(nèi)置關(guān)鍵字的活,所以,這一過程在整個(gè)編譯流程中還是非常重要的,沒有這個(gè)步驟很多關(guān)鍵字其實(shí)就沒有辦法工作。

3) 中間代碼生成

當(dāng)我們將源文件轉(zhuǎn)換成了抽象語(yǔ)法樹、對(duì)整棵樹的語(yǔ)法進(jìn)行解析并進(jìn)行類型檢查之后,就可以認(rèn)為當(dāng)前文件中的代碼基本上不存在無法編譯或者語(yǔ)法錯(cuò)誤的問題了,Go語(yǔ)言的編譯器就會(huì)將輸入的 AST 轉(zhuǎn)換成中間代碼。
Go語(yǔ)言編譯器的中間代碼使用了 SSA(Static Single Assignment Form) 的特性,如果我們?cè)谥虚g代碼生成的過程中使用這種特性,就能夠比較容易的分析出代碼中的無用變量和片段并對(duì)代碼進(jìn)行優(yōu)化。
在類型檢查之后,就會(huì)通過一個(gè)名為 compileFunctions 的函數(shù)開始對(duì)整個(gè)Go語(yǔ)言項(xiàng)目中的全部函數(shù)進(jìn)行編譯,這些函數(shù)會(huì)在一個(gè)編譯隊(duì)列中等待幾個(gè)后端工作協(xié)程的消費(fèi),這些 Goroutine 會(huì)將所有函數(shù)對(duì)應(yīng)的 AST 轉(zhuǎn)換成使用 SSA 特性的中間代碼。

4) 機(jī)器碼生成

Go語(yǔ)言源代碼的 cmd/compile/internal 中包含了非常多機(jī)器碼生成相關(guān)的包,不同類型的 CPU 分別使用了不同的包進(jìn)行生成 amd64、arm、arm64、mips、mips64、ppc64、s390x、x86 和 wasm,也就是說Go語(yǔ)言能夠在上述的 CPU 指令集類型上運(yùn)行,其中比較有趣的就是 WebAssembly 了。
作為一種在棧虛擬機(jī)上使用的二進(jìn)制指令格式,它的設(shè)計(jì)的主要目標(biāo)就是在 Web 瀏覽器上提供一種具有高可移植性的目標(biāo)語(yǔ)言。Go語(yǔ)言的編譯器既然能夠生成 WASM 格式的指令,那么就能夠運(yùn)行在常見的主流瀏覽器中。

$ GOARCH=wasm GOOS=js go build -o lib.wasm main.go

我們可以使用上述的命令將 Go 的源代碼編譯成能夠在瀏覽器上運(yùn)行的匯編語(yǔ)言,除了這種新興的指令之外,Go語(yǔ)言還支持了幾乎全部常見的 CPU 指令集類型,也就是說它編譯出的機(jī)器碼能夠在使用上述指令集的機(jī)器上運(yùn)行。

編譯器入口

Go語(yǔ)言的編譯器入口在 src/cmd/compile/internal/gc 包中的 main.go 文件,這個(gè) 600 多行的 Main 函數(shù)就是Go語(yǔ)言編譯器的主程序,這個(gè)函數(shù)會(huì)先獲取命令行傳入的參數(shù)并更新編譯的選項(xiàng)和配置,隨后就會(huì)開始運(yùn)行 parseFiles 函數(shù)對(duì)輸入的所有文件進(jìn)行詞法與語(yǔ)法分析得到文件對(duì)應(yīng)的抽象語(yǔ)法樹:

func Main(archInit func(*Arch)) { // ... lines := parseFiles(flag.Args())

接下來就會(huì)分九個(gè)階段對(duì)抽象語(yǔ)法樹進(jìn)行更新和編譯,就像我們?cè)谏厦娼榻B的,整個(gè)過程會(huì)經(jīng)歷類型檢查、SSA 中間代碼生成以及機(jī)器碼生成三個(gè)部分:

  • 檢查常量、類型和函數(shù)的類型;
  • 處理變量的賦值;
  • 對(duì)函數(shù)的主體進(jìn)行類型檢查;
  • 決定如何捕獲變量;
  • 檢查內(nèi)聯(lián)函數(shù)的類型;
  • 進(jìn)行逃逸分析;
  • 將閉包的主體轉(zhuǎn)換成引用的捕獲變量;
  • 編譯頂層函數(shù);
  • 檢查外部依賴的聲明;


了解了剩下的編譯過程之后,我們重新回到詞法和語(yǔ)法分析后的具體流程,在這里編譯器會(huì)對(duì)生成語(yǔ)法樹中的節(jié)點(diǎn)執(zhí)行類型檢查,除了常量、類型和函數(shù)這些頂層聲明之外,它還會(huì)對(duì)變量的賦值語(yǔ)句、函數(shù)主體等結(jié)構(gòu)進(jìn)行檢查:

for i := 0; i < len(xtop); i++ { n := xtop[i] if op := n.Op; op != ODCL && op != OAS && op != OAS2 && (op != ODCLTYPE || !n.Left.Name.Param.Alias) { xtop[i] = typecheck(n, ctxStmt) }}for i := 0; i < len(xtop); i++ { n := xtop[i] if op := n.Op; op == ODCL || op == OAS || op == OAS2 || op == ODCLTYPE && n.Left.Name.Param.Alias { xtop[i] = typecheck(n, ctxStmt) }}for i := 0; i < len(xtop); i++ { n := xtop[i] if op := n.Op; op == ODCLFUNC || op == OCLOSURE { typecheckslice(Curfn.Nbody.Slice(), ctxStmt) }}checkMapKeys()for _, n := range xtop { if n.Op == ODCLFUNC && n.Func.Closure != nil { capturevars(n) }}escapes(xtop)for _, n := range xtop { if n.Op == ODCLFUNC && n.Func.Closure != nil { transformclosure(n) }}

類型檢查會(huì)對(duì)傳入節(jié)點(diǎn)的子節(jié)點(diǎn)進(jìn)行遍歷,這個(gè)過程會(huì)對(duì) make 等關(guān)鍵字進(jìn)行展開和重寫,類型檢查結(jié)束之后并沒有輸出新的數(shù)據(jù)結(jié)構(gòu),只是改變了語(yǔ)法樹中的一些節(jié)點(diǎn),同時(shí)這個(gè)過程的結(jié)束也意味著源代碼中已經(jīng)不存在語(yǔ)法錯(cuò)誤和類型錯(cuò)誤,中間代碼和機(jī)器碼也都可以正常的生成了。

initssaconfig() peekitabs() for i := 0; i < len(xtop); i++ { n := xtop[i] if n.Op == ODCLFUNC { funccompile(n) } } compileFunctions() for i, n := range externdcl { if n.Op == ONAME { externdcl[i] = typecheck(externdcl[i], ctxExpr) } } checkMapKeys()}

在主程序運(yùn)行的最后,會(huì)將頂層的函數(shù)編譯成中間代碼并根據(jù)目標(biāo)的 CPU 架構(gòu)生成機(jī)器碼,不過這里其實(shí)也可能會(huì)再次對(duì)外部依賴進(jìn)行類型檢查以驗(yàn)證正確性。

總結(jié)

Go語(yǔ)言的編譯過程其實(shí)是非常有趣并且值得學(xué)習(xí)的,通過對(duì)Go語(yǔ)言四個(gè)編譯階段的分析和對(duì)編譯器主函數(shù)的梳理,我們能夠?qū)?Golang 的實(shí)現(xiàn)有一些基本的理解,掌握編譯的過程之后,Go語(yǔ)言對(duì)于我們來講也不再是一個(gè)黑盒,所以學(xué)習(xí)其編譯原理的過程還是非常讓人著迷的。

總結(jié)

以上是生活随笔為你收集整理的cmd编译可以通过执行没有结果_Go语言是如何完成编译的的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。