LLVM教程( 三)-- LLVM IR

傳統編譯器的設計

<1> 最受歡迎的設計傳統的靜態編譯器(像大多數C編譯器)是三個階段主要組件的前端設計,優化器和後端(下圖)。前端解析代碼,檢查錯誤,並構建一個特定於語言的抽象語法樹(AST)來表示輸入代碼。AST是優化選擇轉換爲一種新的表示方法,優化器和後端上運行代碼。
這裏寫圖片描述

<2> 優化器負責做各種各樣的轉換來提高代碼的運行時間,如消除冗餘計算,通常是或多或少獨立於語言和目標。後端(也稱爲代碼生成器)然後映射到目標指令集的代碼。除了要生成正確的代碼,它還負責生成好的代碼,利用不同尋常的特性支持的體系結構。常見的編譯器後端部分包括指令選擇、寄存器分配和指令調度。
這個模型同樣適用於解釋器和JIT編譯器。Java虛擬機(JVM)也是此模型的一個實現,它使用Java字節碼作爲前端和優化器之間的接口。

<3> 這種經典設計的最重要的勝利來自編譯器決定支持多種源語言或目標體系結構。如果編譯器優化器使用一個共同的代碼表示,那麼可以爲任何語言編寫的一個前端,可以編譯,和後端可以寫任何目標都可以編譯。如下圖
這裏寫圖片描述

通過這個設計,移植編譯器支持新的源語言(如 Algol 或者 BASIC)需要實現一個新的前端,但現有的優化和後端可以重用。如果這些部件不分離,實現一個新的源語言需要從頭重新開始,所以支持N的目標需要N * M和M源語言編譯器。

三端設計的另一個優點是編譯器提供了更廣泛的程序員集合,而不是隻支持一種源語言和一個目標。 對於開源項目,這意味着有一個更大的潛在貢獻者社區,這自然導致對編譯器的更多改進和改進。 這就是爲什麼開放源代碼編譯器服務於許多社區(如GCC)傾向於生成更好的優化機器代碼比較窄的編譯器,如FreePASCAL。 專有編譯器不是這樣,其質量與項目的預算直接相關。 例如,英特爾ICC編譯器以其生成的代碼質量而廣爲人知,即使它爲狹窄的人羣提供服務。

最後一個三端設計的重大勝利,實現前端所需的技能是不依賴所需的優化和後端。分離這些更容易“前端”加強和維護他們的編譯器的一部分。雖然這是一個社會問題,而不是一個技術問題,這就更至關重要了。在實踐中,特別是對於開放源碼項目,想貢獻儘可能減少障礙。

雖然三端設計的好處在編譯器教科書中是令人信服的和有據可查的,但實際上它幾乎從未完全實現。查看開源語言實現(LLVM啓動時),您會發現Perl,Python,Ruby和Java的實現沒有共享代碼。此外,類似Glasgow Haskell編譯器(GHC)和FreeBASIC的項目可以重定向到多個不同的CPU,但是它們的實現非常特定於他們支持的一種源語言。還有各種專用編譯器技術,用於實現用於圖像處理的JIT編譯器,正則表達式,顯卡驅動程序和需要CPU密集型工作的其他子域。

也就是說,這個模型有三個主要的成功案例,第一個是Java和.NET虛擬機。這些系統提供了JIT編譯器,運行時支持和非常清晰的字節碼格式。這意味着任何可以編譯爲字節碼格式的語言可以利用放入優化器和JIT以及運行時的支持。折衷是這些實現在運行時的選擇上提供了很少的靈活性:它們有效地強制JIT編譯,垃圾收集和使用非常特定的對象模型。當編譯與此模型不匹配的語言(例如C(例如,使用LLJVM項目))時,這導致次優性能。

第二個成功案例可能是最不幸的,但也是最常用的重用編譯器技術的方法:將輸入源翻譯爲C代碼(或其他語言),並通過現有的C編譯器發送它。這允許重用優化器和代碼生成器,提供良好的靈活性,對運行時的控制,並且前端實現者很容易理解,實現和維護。不幸的是,這樣做阻礙了異常處理的有效實現,提供了差的調試經驗,減慢編譯,並且對於需要有保證的尾調用(或C不支持的其他特性)的語言可能有問題。

這個模型的最終成功實現是GCC 4。GCC支持許多前端和後端,並且具有活躍和廣泛的貢獻者團體。GCC有一個悠久的歷史,作爲一個C編譯器支持多個目標與hacky支持其他幾種語言。隨着時間的推移,GCC社區正在慢慢演變一個更乾淨的設計。從GCC 4.4開始,它有一個新的優化器表示(稱爲“GIMPLE元組”),它比前面的表示更接近於前端表示。此外,它的Fortran和Ada前端使用一個乾淨的AST。

雖然非常成功,但這三種方法對它們可以用於什麼有很大的侷限性,因爲它們被設計爲單片應用。作爲一個例子,將GCC嵌入其他應用程序,使用GCC作爲運行時/ JIT編譯器,或者提取和重用GCC的片段而不牽引大部分編譯器是不現實可能的。想要使用GCC的C ++前端進行文檔生成,代碼索引,重構和靜態分析工具的人不得不使用GCC作爲一個整體應用程序,以XML形式發佈有趣的信息,或者編寫插件以將外部代碼注入GCC進程。

有多個原因導致GCC不能作爲庫重用,包括猖獗地使用全局變量,弱強制的不變式,設計不良的數據結構,擴展的代碼庫,以及使用宏來阻止代碼庫編譯支持更多每次一個前端/目標對。然而,最難解決的問題是源於其早期設計和年代的固有的建築問題。具體來說,GCC遭受分層問題和漏洞抽象:後端走前端AST生成調試信息,前端生成後端數據結構,整個編譯器依賴於命令行接口設置的全局數據結構。

LLVM IR

<1> 有了歷史背景和上下文,讓我們來看看LLVM:它的設計的最重要的方面是LLVM中間表示(IR),它是用來在編譯器中表示代碼的形式。LLVM IR設計爲託管在編譯器的優化器部分中找到的中級分析和轉換。它被設計成具有許多具體的目標,包括支持輕量級運行時優化,跨功能/過程間優化,整個程序分析和積極的重組轉換等。然而,它的最重要的方面是它本身被定義爲一種具有明確定義的語義的第一類語言。下圖有個例子.ll文件。

define i32 @add1(i32 %a, i32 %b) {
entry:
  %tmp1 = add i32 %a, %b
  ret i32 %tmp1
}

define i32 @add2(i32 %a, i32 %b) {
entry:
  %tmp1 = icmp eq i32 %a, 0
  br i1 %tmp1, label %done, label %recurse

recurse:
  %tmp2 = sub i32 %a, 1
  %tmp3 = add i32 %b, 1
  %tmp4 = call i32 @add2(i32 %tmp2, i32 %tmp3)
  ret i32 %tmp4

done:
  ret i32 %b
}

LLVM 對應的C代碼。

unsigned add1(unsigned a, unsigned b) {
  return a+b;
}

// Perhaps not the most efficient way to add two numbers.
unsigned add2(unsigned a, unsigned b) {
  if (a == 0) return b;
  return add2(a-1, b+1);
}

從這個例子可以看出,LLVM IR是一個低級RISC類的虛擬指令集。像真正的RISC指令集一樣,它支持簡單指令的線性序列,如加,減,比較和分支。這些指令採用三種地址形式,這意味着它們需要一些輸入並在不同的寄存器中產生結果。LLVM IR支持標籤,通常看起來像一個奇怪的彙編語言形式。

與大多數RISC指令集不同,LLVM是使用簡單類型系統強制類型化的(例如,i32是一個32位整數,i32** 是指向32位整數的指針),並且機器的一些細節被抽象掉。例如,調用約定是通過抽象call和ret說明和明確的參數。與機器代碼的另一個顯着區別是,LLVM IR不使用一組固定的命名寄存器,它使用以%字符命名的無限臨時集。

除了被實現爲語言之外,LLVM IR實際上以三種同構形式定義:上面的文本格式,通過優化本身檢查和修改的內存數據結構,以及高效和密集的磁盤上二進制“位碼”格式。該項目LLVM還提供工具,以磁盤格式從文本到二進制轉換:llvm-as文本彙編.ll文件轉換成 .bc包含bitcode粘性物質從中文件,並llvm-dis把一個 .bc文件到一個.ll文件中。

LLVM的三端設計實現

在基於LLVM的編譯器中,前端負責解析,驗證和診斷輸入代碼中的錯誤,然後將解析的代碼轉換爲LLVM IR(通常但不總是通過構建AST,然後將AST轉換爲LLVM IR)。該IR可選地通過一系列分析和優化過程來饋送,改進代碼,然後發送到代碼生成器中以產生本地機器代碼,如圖所示。這是一個非常直接的三端設計實現,但這個簡單的描述掩蓋了LLVM架構從LLVM IR派生的一些功能和靈活性。
這裏寫圖片描述

特別地,LLVM IR是優化器的唯一接口。 這個就意味着你需要知道爲LLVM編寫一個前端是什麼LLVM IR,它是如何工作,以及它期望的不變性。 由於LLVM IR具有一級文本形式,因此構建一個將LLVM IR作爲文本輸出的前端是可能的,也是合理的,然後使用Unix管道通過您選擇的優化器序列和代碼生成器發送它。

這可能是令人驚訝的,但這實際上是LLVM的一個非常新穎的屬性和其成功在廣泛的不同應用程序的主要原因之一。 即使是廣泛成功和相對良好架構的GCC編譯器也沒有這個屬性:它的GIMPLE中級表示不是一個自包含的表示。 作爲一個簡單的例子,當GCC代碼生成器發出DWARF調試信息時,它返回並遍歷源級“樹”形式。 GIMPLE本身使用代碼中的操作的“元組”表示,但(至少如GCC 4.5)仍然表示操作數作爲回到源級樹形式的引用。

參考資料:
http://www.aosabook.org/en/llvm.html
http://llvm.org/docs/LangRef.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章