LLVM是用來設計Compiler很重要的工具。在量子計算的程式設計中,很大部份的軟體研究都是著重於量子計算程式的優化 (Quantum Circuit Optimization)。因此Quantum Compiler的設計也很重要,而使用LLVM來設計Quantum Compiler是很常見的。因此這篇文章LLVM教學將會介紹LLVM的基本架構。
這邊文章主要是根據LLVM的這則介紹影片所改寫而成。有興趣的人歡迎去看原始影片。
什麼是LLVM?
LLVM是一個用來產生編譯器的Open Source Project (https://github.com/llvm/llvm-project)。例如C/C++的Compiler Clang就是一個LLVM所產生的compiler。
在LLVM open source tree中可以看到llvm-project就是此project的根目錄。llvm-project/clang就是作為C/C++/CUDA等程式語言的front-end。llvm-project/llvm就是LLVM的主要核心目錄,包含了middle-end以及backends。
如何Configure和Build LLVM?
Configure LLVM:
只需要使用cmake command即可configure所要編譯的目標項目。例如下面,我們要產生clang以及lld (一個LLVM tool),則我們只需要在LLVM_ENABLE_PROJECTS指定好即可。
cmake /dir/llvm-project/llvm -DLLVM_ENABLE_PROJECTS='clang;lld'
Build LLVM:
make -j
其他在Configure時常用的指令包括 (used with cmake command):
CMAKE_BUILD_TYPE={Release, Debug}
LLVM_CCACHE_BUILD={ON, OFF}
Compilation流程
以下是一個C++程式碼被編譯成為機器碼的流程。一開始利用clang進行frontend處理產生LLVM IR (*.ll)檔案,接著可以使用opt進行middle-end的處理,執行不同的compilation pass去優化程式。接著利用llc產生進行backend處理產生最後的機器碼。
從C到LLVM IR
使用clang以及以下指令。
-S:是產生人能夠讀的形式。
-O3 -disable-llvm-passes:讓compiler準備O3優化但不要執行。
-emit-llvm:讓compiler產生出LLVM IR (*.ll)。
-mem2reg:把memory定址轉換成register定址方便閱讀。
-instnamer:如果instruction沒有名字就給它一個名字方便閱讀。
$ clang -O3 -Xclang -disable-llvm-passes -S -emit-llvm input.c -o input.ll
$ opt -S -mem2reg -instnamer input.ll -o before_opt.ll
LLVM IR內容
這邊顯示一個簡單的C範例
int foo(int a, int b){
return a + b;
}
以下是.ll檔案沒有使用-O3的狀況。target datalayout是backend的格式。而看到i32表示是一個32bits的data type,在原始範例中為int type。這裡可以看到這個Function一開始先產生了%a.addr以及%b.addr的空間,個別為32bits。然後把%a和%b的值分別用store存入%a.addr和%b.addr的位置,接著用load指令將%a.addr和%b.addr的位置的值讀取到register %0與%1中,最後把%0和%1進行相加存到%add,然後return %add為一個i32 type。在LLVM的世界中,可使用的暫存器有無限多個,所以可以隨意使用%0, %1, …%n來產生暫存器。
如果使用-O3來產生LLVM IR,可以看到store與load指令都被直接省略了,因為這個動作是可以被最佳化而不用執行的。現在產生的LLVM IR是直接將%a與%b的值拿來加在一起就存入%add中然後return i32 %add了。
LLVM-IR 結構
LLVM class結構上從llvm::Module開始為最上層,在一個Module裡面會有llvm::GlobalVariable以及llvm::Function。在llvm:Function裡面會有llvm:BasicBlock。llvm::BasicBlock裡面會有llvm::Instruction。而在llvm::Instruction內會有llvm::ICmpInst與llvm::BranchInst。大致如下圖所示。
LLVM-IR是靜態單賦值形式 (Static single-assignment form,SSA-form),也就是每個變數只會被賦值一次,且假設有無窮多可使用的暫存器(Register)。在架構上,一個Module為一連串的GlobalVariables and Functions,一個Function為一連串的Basic Blocks,一個Basic Block為一連串的指令。一個指令就是一系列的運算子與運算元(Operators and operands)。
LLVM-IR的符號
- Global symbols會使用”@”作為變數名稱開頭
- Local symbols會使用”%”作為變數名稱開頭
- Basic block名稱當用到時會使用”%”開頭
- Basic block名稱會使用”:”做為結尾