因為單純就是使用也沒有去動內部的東西,
所以這篇文從 llvm-2.1 一路拖到 2.7 才終於想寫。
對一些早就在用的人來說這內容應該也 lag 好幾年了,
總之都是些沒什麼技術性的東西。
所以我也不分類在 programming 而是 system diary 了。
下載和準備工作
先到這個網頁點進到最新版的 download 連結:
http://llvm.org/releases/
然後抓 LLVM source code 和 Clang source code 這兩個檔案。
接著把 LLVM source code 解開,
再把 Clang source code 解開丟到 llvm 的 tools 目錄下。
Clang source code 解開的目錄名是 clang-2.7 之類的話把它改成 clang,
雖然我也沒有去測試不改會怎樣。
不然 clang+llvm 的 source tree 是沒有附 C++ header files 給你的,
沒設定好的話會連 <iostream> 這種基本 header file 都跟你說找不到。
而號稱可以把 libstdc++ 取代的 libc++ 目前看起來只有支援 MAC 的系統,
所以我還是拿 libstdc++ 來用。
這個網頁的第五點有提到這件事:
http://clang.llvm.org/get_started.html
目前就我手邊的 2.7 版來說,
clang++ 在預設的狀況下就會去偵測 Gentoo 系統上所有 GCC 版本的 header paths,
所以並不是非照要那個網頁的步驟手動設定不可。
不過像我自己有在 ~/gcc45 底下裝 gcc-4.5.1,
而且把 gcc 跟 g++ 都 alias 過去,
那就需要照著做一些額外設定。
你可以學上面的網頁輸入 gcc -v -x c++ /dev/null -fsyntax-only 查一下路徑。
我這邊會看到:
/home/tinlans/gcc45/include/c++/4.5.1
/home/tinlans/gcc45/include/c++/4.5.1/x86_64-pc-linux-gnu
/home/tinlans/gcc45/include/c++/4.5.1/backward
/home/tinlans/gcc45/include
/home/tinlans/gcc45/lib/gcc/x86_64-pc-linux-gnu/4.5.1/include
/home/tinlans/gcc45/lib/gcc/x86_64-pc-linux-gnu/4.5.1/include-fixed
/usr/include
確定路徑以後去編輯 llvm/tools/clang/lib/Frontend/InitHeaderSearch.cpp,
搜尋 InitHeaderSearch::AddDefaultCIncludePaths() 這個 method 應該就大致知道要怎麼改。
我的 host 環境是 Gentoo Linux x86-64,
所以修改的方法是像這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
--- InitHeaderSearch.cpp.orig 2010-09-10 04:24:11.437471297 +0800 +++ InitHeaderSearch.cpp 2010-09-10 04:33:26.355481664 +0800 @@ -577,6 +577,11 @@ "/usr/lib/gcc/x86_64-pc-linux-gnu/4.4.3/include/g++-v4", "x86_64-pc-linux-gnu", "32", "", triple); + // Gentoo x86-64 gcc 4.5.1 in my home directory + AddGnuCPlusPlusIncludePaths( + "/home/tinlans/gcc45/include/c++/4.5.1", + "x86_64-pc-linux-gnu", "32", "", triple); + break; case llvm::Triple::FreeBSD: AddGnuCPlusPlusIncludePaths("/usr/include/c++/4.2", "", "", "", triple); |
如果好奇 AddGnuCPlusPlusIncludePaths() 各參數的意義,
在這個檔案最上面就看得到它的宣告式。
因為我的 GCC 4.5.1 有同時做出 32/64 bit 的 lib,
所以還會有 /home/tinlans/gcc45/include/c++/4.5.1/x86_64-pc-linux-gnu/32 這路徑存在。
而這也是第三個參數填了 "32" 的原因。
編譯和安裝
為了不弄髒 source tree,
建議採用 out-of-source build 的方式編譯。
先到 llvm 以外的地方造一個目錄,
然後再下 cmake 來產生 Makefile。
configure 的下法我就不寫了,
都 2010 年了哪還有傻子看見 CMakeLists.txt 還會去跑 configure?
當然 CMakeLists.txt 沒寫好或亂寫的狀況下例外。
總之要是以後我的小孩同時看到這兩種東西還去下 configure 的話,我一定先打死他。
cmake 的下法這邊以 tcsh 為例:
env CFLAGS=-fno-strict-aliasing CXXFLAGS=-fno-strict-aliasing cmake -i path/to/llvm/source
加上 -i 就會以 wizard mode 啟動 cmake,
高興的話也可以用 cmake-gui 跑。
我知道有人看到 -fno-strict-aliasing 會非常想笑,
但是不加的話光從 warning messages 來看是有一定機率編出壞掉的玩具。
這個問題是出在 clang 的 code 上,
所以如果走 llvm-gcc 這條路的應該不會遇上。
出來的選項可以學這樣填:
Would you like to see advanced options? [No]:<Enter>
Please wait while cmake processes CMakeLists.txt files....Variable Name: CLANG_BUILD_EXAMPLES
Description: Build CLANG example programs.
Current Value: OFF
New Value (Enter to keep current value): <Enter>Variable Name: CLANG_TEST_USE_VG
Description: Run Clang tests under Valgrind
Current Value: OFF
New Value (Enter to keep current value): <Enter>Variable Name: CMAKE_BUILD_TYPE
Description: Choose the type of build, options are: None(CMAKE_CXX_FLAGS or CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel.
Current Value:
New Value (Enter to keep current value): RelWithDebInfoVariable Name: CMAKE_INSTALL_PREFIX
Description: Install path prefix, prepended onto install directories.
Current Value: /usr/local
New Value (Enter to keep current value): 就是 make install 以後會裝進去的路徑Variable Name: C_INCLUDE_DIRS
Description: Colon separated list of directories clang will search for headers.
Current Value:
New Value (Enter to keep current value): <Enter>Variable Name: LLVM_BUILD_32_BITS
Description: Build 32 bits executables and libraries.
Current Value: OFF
New Value (Enter to keep current value): <Enter>Variable Name: LLVM_BUILD_EXAMPLES
Description: Build LLVM example programs.
Current Value: OFF
New Value (Enter to keep current value): <Enter>Variable Name: LLVM_BUILD_TOOLS
Description: Build LLVM tool programs.
Current Value: ON
New Value (Enter to keep current value): <Enter>Variable Name: LLVM_ENABLE_ASSERTIONS
Description: Enable assertions
Current Value: ON
New Value (Enter to keep current value): <Enter>Variable Name: LLVM_ENABLE_PEDANTIC
Description: Compile with pedantic enabled.
Current Value: ON
New Value (Enter to keep current value): OFFVariable Name: LLVM_ENABLE_PIC
Description: Build Position-Independent Code
Current Value: ON
New Value (Enter to keep current value): <Enter>Variable Name: LLVM_ENABLE_THREADS
Description: Use threads if available.
Current Value: ON
New Value (Enter to keep current value): <Enter>Variable Name: LLVM_ENABLE_WARNINGS
Description: Enable compiler warnings.
Current Value: ON
New Value (Enter to keep current value): <Enter>Variable Name: LLVM_ENABLE_WERROR
Description: Fail and stop if a warning is triggered.
Current Value: OFF
New Value (Enter to keep current value): <Enter>Variable Name: LLVM_LIBDIR_SUFFIX
Description: Define suffix of library directory name (32/64)
Current Value:
New Value (Enter to keep current value): <Enter>Variable Name: LLVM_TABLEGEN
Description: Native TableGen executable. Saves building one when cross-compiling.
Current Value: tblgen
New Value (Enter to keep current value): <Enter>Variable Name: LLVM_TARGETS_TO_BUILD
Description: Semicolon-separated list of targets to build, or "all".
Current Value: Alpha;ARM;Blackfin;CBackend;CellSPU;CppBackend;Mips;MBlaze;MSIL;MSP430;PIC16;PowerPC;Sparc;SystemZ;X86;XCore
New Value (Enter to keep current value): ARM;CBackend;CppBackend;Mips;X86 (反正就選高興的用,不然乾脆 all 也行)Variable Name: LLVM_TARGET_ARCH
Description: Set target to use for LLVM JIT or use "host" for automatic detection.
Current Value: host
New Value (Enter to keep current value): <Enter>Variable Name: NM_PATH
Description: Path to a program.
Current Value: /usr/bin/nm
New Value (Enter to keep current value): <Enter>Please wait while cmake processes CMakeLists.txt files....
CMake complete, run make to build project.
然後直接用 GNU make 編譯:
gmake -j9 install
總之不怕被人打的話可以下 -jN 的參數,
N 可以是工作站的 CPU threads 總數 + 1。
因為 VMKit 不支援用 CMake 產生 Makefile,
加上它需要用到 LLVM 的 object tree,
所以你只能使用傳統的 configure 來編譯 LLVM。
這種狀況也很簡單。
方法一就是去罵 VMKit 的人使用落後的 autotools。
方法二就是乖乖下 configure,
像是這樣:
path/to/llvm/source/configure --prefix=安裝路徑 --enable-targets="arm,cbe,cpp,mips,x86,x86_64"
所以總有一天會需要去修改它們的 source code。
使用 OS 提供的套件系統來安裝 LLVM,
是純粹以只會使用它的角度來考量。
若是以一名開發者的角度來考量的話,
就需要學習如何手動編譯及安裝到自己的目錄下。
基本用法
先隨便造一個範例程式:
1 2 3 4 5 6 7 |
#include <stdio.h> int main(int argc, char **argv) { printf("hello world\n"); return 0; } |
直接編譯成可執行檔:
clang test.c -o test
這跟 GCC 沒什麼差別。
編譯成 object file:
clang -c test.c
會得到 test.o,
這也跟 GCC 沒什麼差別。
輸出 assembly code:
clang -S test.c
會得到 test.s,
還是跟 GCC 沒有什麼差別。
接下來就是 LLVM 特有的東西了。
-c 搭配 -emit-llvm 會輸出 bitcode file,
你可以把它想成是 target 在 llvm 這平台上的 object file。
-S 搭配 -emit-llvm 會輸出所謂的 LLVM assembly code。
編譯成 bitcode file:
clang -c -emit-llvm test.c -o test.bc
如果你不加後面的 -o test.bc 參數,
目前我手邊的版本會輸出 test.o 這樣的檔名。
雖然說 .bc 這種 suffix 只是一個建議的命名慣例,
而且 llvm 的 command tools 也不會因為 suffix 是 .o 而搞混它,
但為了避免人類搞混我建議還是把它輸出成帶 .bc suffix 的檔名。
這個檔案用系統的 file 指令測試的話,
應該會告訴你它是個 data 而不是 object file。
輸出 LLVM assembly code:
clang -S -emit-llvm test.c -o test.ll
用 -o test.ll 的理由同上,
不加的話你會得到 test.s 這種檔名而容易跟一般 assembly file 混淆。
得到的檔案內容會長這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
; ModuleID = 'test.c' target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64" target triple = "x86_64-unknown-linux-gnu" @.str = private constant [13 x i8] c"hello world\0A\00" ; <[13 x i8]*> [#uses=1] define i32 @main(i32 %argc, i8** %argv) nounwind { entry: %retval = alloca i32, align 4 ; <i32*> [#uses=2] %argc.addr = alloca i32, align 4 ; <i32*> [#uses=1] %argv.addr = alloca i8**, align 8 ; <i8***> [#uses=1] store i32 0, i32* %retval store i32 %argc, i32* %argc.addr store i8** %argv, i8*** %argv.addr %call = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([13 x i8]* @.str, i32 0, i32 0)) ; <i32> [#uses=0] %0 = load i32* %retval ; <i32> [#uses=1] ret i32 %0 } declare i32 @printf(i8*, ...) |
好奇怎麼讀它的話可以參考這個網頁:
http://llvm.org/docs/LangRef.html
以直譯的方式 (含 JIT 功能) 直接執行 bitcode file:
lli test.bc
把 bitcode file 反組譯回 LLVM assembly code:
llvm-dis test.bc
這指令會自動輸出 test.ll 這種檔名,
所以不必像上面那樣特地加參數指定。
組譯 LLVM assembly code:
llvm-as test.ll
這會得到一個新的 test.bc。
多檔編譯與連結
先擺兩個測試程式:
1 2 3 4 5 6 7 |
void foo(); int main(int argc, char **argv) { foo(); return 0; } |
1 2 3 4 5 6 |
#include <stdio.h> void foo() { printf("hello world\n"); } |
編譯:
clang -c -emit-llvm test1.c -o test1.bc
clang -c -emit-llvm test2.c -o test2.bc
連結:
llvm-link test1.bc test2.bc -o test.bc
執行:
lli test.bc
> lli test1.bc
LLVM ERROR: Program used external function 'foo' which could not be resolved!
> lli test2.bc
'main' function not found in module.
函式庫的製作與連結
直接沿用上面的例子。
把 test2.bc 封裝成 libTest.a:
llvm-ar rucs libTest.a test2.bc
當然這個並不是普通的 archive file。
查看 symbol table:
llvm-nm libTest.a
連結:
llvm-ld test1.bc -o test -lTest
這樣會得到兩個檔案。
一個是 test.bc,
你可以用 lli test.bc 去執行它。
另一個是叫 test 的 script 檔,
大概長這樣:
1 2 3 4 |
#!/bin/sh lli=${LLVMINTERP-lli} exec $lli \ test.bc ${1+"$@"} |
其實也不過就是自動叫 lli 幫你直譯而已。
如果你不希望它是一個 script 檔而是真正的可執行檔,
加上 -native 參數就可以了。
Backend Compiler
如果說 LLVM assembly code 是 Open64 的 WHIRL 的話,
llc 的地位大致上相當於 Open64 的 be。
llc 的主要目的是把 bitcode 編譯成特定平台的 assembly code,
而要輸出哪個平台可以用 llc -version 查看:
Low Level Virtual Machine (http://llvm.org/):
llvm version 2.7svn
Optimized build with assertions.
Built Sep 10 2010 (02:45:40).
Host: x86_64-unknown-linux-gnu
Host CPU: core2Registered Targets:
arm - ARM
c - C backend
cpp - C++ backend
mips - Mips
mipsel - Mipsel
thumb - Thumb
x86 - 32-bit X86: Pentium-Pro and above
x86-64 - 64-bit X86: EM64T and AMD64
因為我在 cmake -i 的時候有刪掉一些不要的 targets,
所以這裡可用的 targets 會比用預設值編 LLVM 的人少一些。
值得注意的是 llc 也有 C 跟 C++ 的 targets。
其實這個也不用太意外,
Open64 也是有 whirl2c 等一系列的工具。
不過相較於大部分 target 一改就要整套重編一份的 compiler 來說,
LLVM 在這方面算是先進多了。
也因此有些人被這種功能唬住而神化了 LLVM。
但其實這不過只是軟體系統規劃能力的差別罷了。
舉個例子,
將 bitcode 編譯成 ARM 的 assembly code:
llc -march=arm test.bc
這會得到 test.s。
不過當 target 是 c 或 cpp 時 suffix 就不是 .s。