LLVM 簡介 (一)


LLVM項目

LLVM是一個開源的項目,是一個編譯器框架,是一系列模塊化、可重用的編譯器以及工具鏈技術的集合。

LLVM的核心是LLVM庫。同時LLVM還實現了一些周邊工具。

LLVM的一個設計思想是優化可以滲透在整個編譯流程中各個階段,比如編譯時、鏈接時、運行時等。

 

你可以基於LLVM提供的功能開發自己的模塊,並集成在LLVM系統上,增加它的功能;或者利用LLVM來支撐底層實現,開發自己的工具。它可以很容易和IDE集成(因為IDE軟件可以直接調用庫來實現一些如靜態檢查這些功能),也很容易構建生成各種功能的工具(因為新的工具只需要調用需要的庫就行)。

可見  https://github.com/llvm/llvm-project    https://llvm.org/docs/GettingStarted.html

 

LLVM最初代表Low Level Virtual Machine, 但是隨着LLVM家族越龐大,這個最初的意思已經不適用。llvm.org 上的介紹很明確, LLVM就是這個項目的全稱。

 

LLVM項目最初由UIUC的Vikram 和 Chris Lattner於2000年開始開發。他們的最初的目的是為了靜態和動態編程語言開發一個動態編譯技術。2005年,Chris進入蘋果公司,繼續進行LLVM的開發。2013年,索尼公司也開始使用Clang開發PS4。

在這之前,Apple公司一直使用gcc作為編譯器,后來GCC對Objective-C的語言特性支持一直不夠,Apple自己開發的GCC模塊又很難得到GCC委員會的合並。等到Chris Lattner畢業時,Apple就把他招入靡下,去開發自己的編譯器,所以LLVM最初受到了Apple的大力支持。

 

廣義的LLVM是指整個LLVM架構,狹義的LLVM指的是LLVM后端(包含代碼優化和目標代碼生成)。

 

 

 

Clang

LLVM只是一個編譯器框架,所以還需要一個前端來支撐整個系統,所以Apple研發了Clang,作為整個編譯器的前端,Clang用來編譯C、C++和Objective-C。可以用Clang/LLVM來和gcc做對比

Clang與LLVM的關系如圖

 

相比於GCC,Clang具有如下優點

  • 編譯速度快:在某些平台上,Clang的編譯速度顯著的快過GCC(Debug模式下編譯OC速度比GGC快3倍)
  • 占用內存小:Clang生成的AST所占用的內存是GCC的五分之一左右
  • 模塊化設計:Clang采用基於庫的模塊化設計,易於 IDE 集成及其他用途的重用
  • 診斷信息可讀性強:在編譯過程中,Clang 創建並保留了大量詳細的元數據 (metadata),有利於調試和錯誤報告
  • 設計清晰簡單,容易理解,易於擴展增強

 

當然除了Clang,還可以使用其他前端,見后續介紹。

 

 

LLVM 架構

參考了:http://www.aosabook.org/en/llvm.html   https://www.jianshu.com/p/1367dad95445

傳統編譯器架構

傳統的編譯器框架如下,三個階段:

 

 

 

Frontend:前端,進行詞法分析、語法分析、語義分析、生成中間代碼。 解析源代碼,檢查錯誤,並且把它翻譯為特定語言語法抽象樹(language-specific Abstract Syntax Tree)。AST樹也可以被進一步為了優化轉換為中間表示(比如,LLVM Intermedaite Representaion), 后面的2個階段都使用這個中間表示。

Optimizer:優化器,中間代碼優化,比如去除無用的變量或者無用的計算,來提高代碼運行效率。

Backend:后端。Code Generation,生成目標機器碼(target instruction set)。Backend目標是生成充分可以利用目標機器體系結構的native code。

 這種設計最大的優勢就是解耦合,讓每部分專注於自己的功能。比如如果所有的語言都使用了同一種中間表示,那么意味着可以使用相同的Optimizer。這就大大提高了代碼重復利用率以及Optimizer優化。

LLVM設計思路

傳統架構的缺點

經典的編譯器如gcc在設計上都是提供一條龍服務的: 你不需要知道它使用的IR是什么樣的,它也不會暴露中間接口來給你操作它的IR。 換句話說,從前端到后端,這些編譯器的大量代碼都是強耦合的。

這樣做有好處也有壞處。好處是,因為不需要暴露中間過程的接口,它可以在內部做任何想做的平台相關的優化。

而壞處是,每當一個新的平台出現,這些編譯器都要各自為政實現一個從自己的IR到新平台的后端。 甚至如果當一種新語言出現,且需要實現一個新的編譯器,那么可能需要設計一個新的IR,以及針對大部分平台實現這個IR的后端。 不妨想一下,如果有M種語言、N種目標平台,那么最壞情況下要實現 M*N 個前后端。這是很低效的。

 

因此,我們很自然地會想,如果大家都共用一種IR呢? 那么每當新增加一種語言,我們就只要添加一個這個語言到IR的前端; 每當新增加一種目標平台,我們就只要添加一個IR到這個目標平台的后端。 如果有M種語言、N種目標平台,那么最優情況下我們只要實現 M+N 個前后端。

LLVM就是這樣一個項目。LLVM的核心設計了一個叫 LLVM IR 的中間表示, 並以庫(Library) 的方式提供一系列接口, 為你提供諸如操作IR、生成目標平台代碼等等后端的功能。

LLVM架構

 

特點可以概括為:

 

  • 不同的前端后端使用統一的中間代碼LLVM Intermediate Representation (LLVM IR)
  • 如果需要支持一種新的編程語言,那么只需要實現一個新的前端
  • 如果需要支持一種新的硬件設備,那么只需要實現一個新的后端
  • 優化階段是一個通用的階段,它針對的是統一的LLVM IR,不論是支持新的編程語言,還是支持新的硬件設備,都不需要對優化階段做修改
  • 相比之下,GCC的前端和后端沒分得太開,前端后端耦合在了一起。所以GCC為了支持一門新的語言,或者為了支持一個新的目標平台,就 變得特別困難
  • LLVM現在被作為實現各種靜態和運行時編譯語言的通用基礎架構(C家族、Java、.NET、Python、Ruby、Scheme、Haskell、D等)

 

基於IR的LLVM編譯器是一個強有力和靈活的工具。LLVM編譯器三個階段之間使用IR進行數據交流。

前端

在LLVM編譯器里,前端(front end)會對源代碼進行解析,驗證,診斷錯誤。通常LLVM的前端,會先構建一個語法抽象樹(Abstract Syntax Tree), 然后把AST轉化為LLVM 的中間表示(LLVM Intermedaite Representation)。

這個LLVM IR 中間表示會作為優化分析的pipeline的輸入。

 

 

 注意有一個LLVM IR linker,這個是IR的鏈接器,而不是gcc中的鏈接器。為了實現鏈接時優化,LLVM在前端(Clang)生成單個代碼單元的IR后,將整個工程的IR都鏈接起來,同時做鏈接時優化。

優化

 一個LLVM 優化器是由很多pass組成的流水線(pipeline)。

詳見下面 LLVM PASS 中 LLVM PASS與LLVM Optimizer

后端

LLVM backend產生目標機器碼。

 

LLVM backend就是LLVM真正的后端,也被稱為LLVM核心,包括編譯、匯編、鏈接,最后生成匯編文件或者目標碼。這里的LLVM compiler和gcc中的compiler不一樣,這里的LLVM compiler只是編譯LLVM IR。

詳見下面的 LLVM PASS與LLVM Code Genarator

 

LLVM各部分與gcc的對應關系

gcc的編譯器,輸入是源代碼,輸出是匯編代碼,相當於是LLVM中Clang一級加上IR linker再加上LLVM compiler中的生成匯編代碼部分(Clang輸出可執行文件的一條龍過程,不會生成匯編文件,內部全部走中間表示,生成匯編碼和生成目標文件是並列的)。

gcc的匯編器,輸入是匯編代碼,輸出是目標文件,相當於是LLVM中的llvm-mc(這是另一個工具,Clang一條龍默認不走這個工具,但會調用相同的庫來做匯編指令的下降和發射)。

gcc的鏈接器,輸入是目標文件,輸出是最終可執行文件,相當於LLVM中的Linker,現在LLVM Linker還在開發中(已釋出,叫lld,但仍然不成熟),所以Clang驅動程序調起來的鏈接器還是系統鏈接器,可以選擇使用gcc的ld(這塊會很快變,LLVM社區必然會在lld成熟后默認換上去,大家可以自行驗證)。

參考:https://zhuanlan.zhihu.com/p/140462815

 

LLVM PASS

pass的概念

https://zhuanlan.zhihu.com/p/122522485 https://zhuanlan.zhihu.com/p/77393146

 Pass就是“遍歷一遍IR,可以同時對它做一些操作”的意思。翻譯成中文應該叫“趟”。LLVM Pass 是LLVM最重要的一個概念。Pass代表了一個處理步驟比如內聯函數的替換, 表達式重寫,循環優化。

在實現上,LLVM的核心庫中會給你一些 Pass類 去繼承。

所有的LLVM pass都有一個父類, Pass class。很多LLVM pass是被實現在一個單獨的.cpp文件里。下面的例子是個一個簡單的LLVM Pass例子。LLVM pass需要被定義在一個匿名的命名空間,保持對定義文件的私有可見性。最后一行代碼,用於讓外部可以創建使用這個pass。

namespace {
  class Hello : public FunctionPass {
  public:
    // Print out the names of functions in the LLVM IR being optimized.
    virtual bool runOnFunction(Function &F) {
      cerr << "Hello: " << F.getName() << "\n";
      return false;
    }
  };
}

FunctionPass *createHelloPass() { return new Hello(); }

 

LLVM Pass的作用

  1. 顯然它的一個用處就是插樁: 在Pass遍歷LLVM IR的同時,自然就可以往里面插入新的代碼。
  2. 機器無關的代碼優化:IR在被翻譯成機器碼前會做一些機器無關的優化。 但是不同的優化方法之間需要解耦,所以自然要各自遍歷一遍IR,實現成了一個個LLVM Pass。 最終,基於LLVM的編譯器會在前端生成LLVM IR后調用多個LLVM Pass做機器無關優化, 然后再調用LLVM后端生成目標平台代碼。
  3. 靜態分析: 像VSCode的C/C++插件就會用LLVM Pass來分析代碼,提示可能的錯誤 (無用的變量、無法到達的代碼等等)。

 LLVM PASS與LLVM Optimizer

一個LLVM 優化器是由很多pass組成的流水線(pipeline)

 

 

 

LLVM編譯器優化級別可以用參數指定, -O0 表示不使用任何pass沒有優化,-O3 使用67passes進行優化(LLVM 2.8)。

LLVM優化器提供了很多passes。它們的實現方式類似。所有的passes被編譯到一個或者很多.o文件,然后再被編譯為archive libraries (Linux 系統使用后綴 .a 表示 )。LLVM編譯器使用這些庫文件進行優化。

這些passes之間是松耦合的,盡量不依賴其他的pass。如果有的pass依賴於其他的pass,那么這個pass就要顯示的聲明這些依賴關系。

當我們要使用具有依賴性的pass,LLVM PassManager會使用這些依賴聲明,加載依賴庫,然后執行這個pass。

 

LLVM最強大的地方在於,雖然LLVM提供很多pass,但是如何使用他們取決於用戶。比如下圖一個用戶使用LLVM實現一個新的編譯器XYZoptimizer。這個新的編譯器只使用3 LLVM Pass (A, B, D)。最終的XYZoptimizer.o 不變包含整個LLVM, 它只鏈接它使用的pass。

 

LLVM PASS 與 LLVM Code Generator

Code Generator把LLVM IR編譯成為目標機器碼。

每一個不同硬件平台的Code Generator都不會一樣,但是它們也有很多相似的操作。比如寄存器分配, 盡管每一個硬件平台不一樣,但是分配算法是通用的。

所以Code Generator因此也可以被分成不同的獨立的passes, 指令選擇,寄存器分配,調度,代碼布局優化和匯編語言生成。LLVM為Code Generator提供了很多passes。

用戶可以自己選擇不同的pass,重寫默認實現或者增加自己的pass。這種組裝的靈活性讓用戶不必從頭實現Code Generator。

對於不同平台不同的地方, LLVM提供了目標表述文件(.td 文件)。LLVM使用tblgen工具來處理這些描述文件。

 

LLVM IR

LLVM IR之所以流行, 其原因之一是,它是一個完備強大的代碼中間表示。每一階段基於LLVM IR做自己的事情,無需去關注前階段,或者后階段的事情。作為一個反例, 比較成功的GCC編譯器無法做到這一點。GCC編譯器代碼的中間表示是不完備的。比如它的優化過程,在某些情況下(提供DWARF debug信息),需要回溯到上一階段。

這部分可見 http://www.aosabook.org/en/llvm.html  https://llvm.org/docs/LangRef.html

 

在LLVM中,IR有三種表示,一種是可讀的IR,類似於匯編代碼,但其實它介於高等語言和匯編之間,這種表示就是給人看的,磁盤文件后綴為.ll;第二種是不可讀的二進制IR,被稱作位碼(bitcode),磁盤文件后綴為.bc;第三種表示是一種內存格式,只保存在內存中,這種格式是LLVM之所以編譯快的一個原因,它不像gcc,每個階段結束會生成一些中間過程文件,它編譯的中間數據都是這第三種表示的IR。三種格式是完全等價的,我們可以在Clang/LLVM工具的參數中指定生成這些文件(默認不生成,對於非編譯器開發人員來說,也沒必要生成),可以通過llvm-asllvm-dis來在前兩種文件之間做轉換。

 

LLVM模塊化設計的好處

LLVM IR是可以被序列化的形式存儲為bitcode文件。並且因為LLVM IR不需要依賴其他東西(self-contained), 所以這個序列化的存儲,是耗費很小的。

我們可以存儲優化過程中的LLVM IR中間結果,在將來的某個點繼續加載進行處理。這個特征就讓LLVM支持Link-Time Optimization(LTO)和 Install-Time optimization。

 

LTO解決了傳統編譯器只能掃描一遍要翻譯的單元,不能做跨文件的優化的問題。

LLVM編譯器(比如Clang)可以把LLVM IR 的bitcode表示,寫到.o文件里,而不是本地機器碼。把本地機器碼的產生推遲到link time。如下圖所示,在link time,可以做cross-boundaries的優化。

 

 Intall-Time 把LLVM backend從link time 分離出來。

這樣做的好處是,比如使用不同平台的用戶可以下載相同的link time的輸出,在用戶本地翻譯長目標機器碼,如下圖所示。

 

 對於編譯的測試,LLVM也具有很大的優勢。

編譯器非常復雜,GCC測試的時候,很難確定是什么引起測試失敗。但是LLVM編譯器可以基於LLVM IR進行單元測試。

在測試的時候,一旦發現一個測試失敗。修復這個bug的第一步是,在得到一個可以重現這個bug的測試用例。一旦,找到這個測試用例,就要逐漸減小的它的范圍,從而知道哪一部分包含了這個bug。

人工完成這個過程非常繁瑣,LLVM提供一個BugPoint的工具來做這個事情。

 

LLVM工具

LLVM的核心是一些庫,而不是一個具體的二進制程序。 不過,LLVM這個項目本身也基於這個庫實現了周邊的工具

LLVM工具用來調用LLVM的一部分庫,實現庫的功能,通常使用編譯器的人會調用到這些工具。   注意區分庫和工具的概念,工具通過調用庫實現功能。

主要可以參考https://llvm.org/docs/GettingStarted.html#llvm-tools 。以及https://llvm.org/docs/CommandGuide/、、https://llvm.org/

bugpoint

bugpoint is used to debug optimization passes or code generation backends by narrowing down the given test case to the minimum number of passes and/or instructions that still cause a problem, whether it is a crash or miscompilation. See HowToSubmitABug.html for more information on using bugpoint.
llvm-ar

The archiver produces an archive containing the given LLVM bitcode files, optionally with an index for faster lookup.
llvm-as

The assembler transforms the human readable LLVM assembly to LLVM bitcode.
llvm-dis

The disassembler transforms the LLVM bitcode to human readable LLVM assembly.
llvm-link

llvm-link, not surprisingly, links multiple LLVM modules into a single program.
lli

lli is the LLVM interpreter, which can directly execute LLVM bitcode (although very slowly…). For architectures that support it (currently x86, Sparc, and PowerPC), by default, lli will function as a Just-In-Time compiler (if the functionality was compiled in), and will execute the code much faster than the interpreter.
llc

llc is the LLVM backend compiler, which translates LLVM bitcode to a native code assembly file.
opt

opt reads LLVM bitcode, applies a series of LLVM to LLVM transformations (which are specified on the command line), and outputs the resultant bitcode. ‘opt -help’ is a good way to get a list of the program transformations available in LLVM.

opt can also run a specific analysis on an input LLVM bitcode file and print the results. Primarily useful for debugging analyses, or familiarizing yourself with what an analysis does.

 

以及Clang

Clang是現在LLVM項目中一個很重要的前端工具。clang能夠調用起來整個編譯器的流程,也就是上邊其他工具調用的庫,它很多都同樣會調用。clang通過指定-emit-llvm參數,可以配合-S-c生成.ll.bc文件,這樣我們就能把Clang的部分和LLVM的后端分離開來獨立運行,對於觀察編譯器流程來說,很實用。

clang -emit-llvm -c main.c -o main.bc
clang -emit-llvm -S main.c -o main.ll

 

 

概念上的一詞多義

https://zhuanlan.zhihu.com/p/140462815

LLVM

  • LLVM項目或基礎架構:這是對整個LLVM編譯器框架的程序,包括了前端、優化器、后端、匯編器、鏈接器,以及libc++、JIT等。上下文如:“LLVM項目由以下幾個模塊組成”。
  • 基於LLVM開發的編譯器:這是指一部分或全部基於LLVM項目開發的編譯器軟件,軟件可能基於LLVM的前端或后端來實現。上下文如:“我用LLVM將C語言編譯到MIPS平台”。
  • LLVM庫:LLVM項目由庫代碼和一些工具組成,有時會指代LLVM庫內容。上下文如:“我的項目使用了LLVM的即時編譯框架”(JIT是其中一個庫)。
  • LLVM核心:在IR和后端算法上的內容,就是LLVM核心,也就是通常Clang/LLVM中的LLVM。上下文如:“LLVM和Clang是兩個項目”。
  • LLVM IR:有些時候也會指代其中間表示。上下文如:“Clang是一個前端,能將源代碼翻譯成LLVM”。

Clang

通常我們在命令行上調用的clang工具,是Clang驅動程序,因為LLVM本質上只是一個編譯器框架,所以需要一個驅動程序把整個編譯器的功能串起來,clang能夠監控整個編譯器的流程,即能夠調用到Clang和LLVM的各種庫,最終實現編譯的功能。

 

參考:

官方文檔:

http://www.aosabook.org/en/llvm.html 

https://llvm.org/docs/index.html

https://llvm.org/docs/GettingStarted.html

https://llvm.org/pubs/2008-10-04-ACAT-LLVM-Intro.pdf

https://llvm.org/docs/LangRef.html

https://github.com/llvm/llvm-project

博客:

https://zhuanlan.zhihu.com/p/75723370

https://zhuanlan.zhihu.com/p/77393146

https://www.jianshu.com/p/1367dad95445

https://zhuanlan.zhihu.com/p/140462815

https://zhuanlan.zhihu.com/p/122522485

https://bbs.pediy.com/thread-251326.htm

后續學習資料:

http://www.cs.cmu.edu/afs/cs.cmu.edu/academic/class/15745-s14/public/lectures/

https://www.zhihu.com/column/c_1250484713606819840


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM