Calendar

九月 2010
« 八月   十一月 »
 12345
6789101112
13141516171819
20212223242526
27282930  

Categories

拖稿很久的 LLVM 使用文

因為單純就是使用也沒有去動內部的東西,
所以這篇文從 llvm-2.1 一路拖到 2.7 才終於想寫。
對一些早就在用的人來說這內容應該也 lag 好幾年了,
總之都是些沒什麼技術性的東西。
所以我也不分類在 programming 而是 system diary 了。

下載和準備工作

先到這個網頁點進到最新版的 download 連結:
http://llvm.org/releases/
然後抓 LLVM source codeClang source code 這兩個檔案。
接著把 LLVM source code 解開,
再把 Clang source code 解開丟到 llvm 的 tools 目錄下。
Clang source code 解開的目錄名是 clang-2.7 之類的話把它改成 clang,
雖然我也沒有去測試不改會怎樣。

打算使用 clang++ 編譯 C++ 程式的注意事項
clang++ 可以直接吃系統上的 libstdc++ header files。
不然 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,
所以修改的方法是像這樣:

如果好奇 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): RelWithDebInfo

Variable 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): OFF

Variable 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。

無法使用 CMake 的狀況
如果你有打算搭 VMKit 之類的東西,
因為 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"

學習自己編譯和安裝的理由
因為我本身是做 compiler 的,
所以總有一天會需要去修改它們的 source code。
使用 OS 提供的套件系統來安裝 LLVM,
是純粹以只會使用它的角度來考量。
若是以一名開發者的角度來考量的話,
就需要學習如何手動編譯及安裝到自己的目錄下。

基本用法

先隨便造一個範例程式:

直接編譯成可執行檔:

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 混淆。
得到的檔案內容會長這樣:

好奇怎麼讀它的話可以參考這個網頁:
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。

多檔編譯與連結

先擺兩個測試程式:

編譯:

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

單獨執行 test1.bc 和 test2.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 檔,
大概長這樣:

其實也不過就是自動叫 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: core2

Registered 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。

  • Mouse

    有機會也要來玩玩

  • class AX
    {
    public:
    AX(){}
    ~AX(){}
    };

    AX gax;

    int main()
    {
    return 34;
    }
    请问这个我用clang编译的 用lli运行会说__dso_handle 没有,请问应该怎么办?谢谢!!

    • 沒遇過。
      要用 clang++,
      而且編譯 clang 的時候需要指定 libstdc++ 的路徑。
      這些確定都有做好嗎?

  • Hi, sorry for enquiring this query here, but I can’t find a contact form or something so I thought I leave my enquiry here. I run a blogengine blog but I am receiving increased amounts of spam. I see u use wordpress, is it painless to regulate spam with wordpress or doesn't it make any difference? I hope you will respond to my comment or maybe send me an email with your answer if you this is the wrong place. Best regards, Annie

    • Please try to find a very old plugin: "Spam Karma 2".
      It's very useful, but it's no longer maintained by its author.

  • The source code of HelenOS was modified to successfully compile with Clang, and passed all kernel and user space regression tests on IA-32 .

  • This page describes a simple way to get started with llvm/clang for MIPS, llvm/clang are not self-contained and require a gnu toolchain to generates code for MIPS.

  • descent

    你好,

    得知版主的工作和編譯器相關, 有個問題想請教:
    g++ 4.4.3 似乎對 global object 會產生
    _Z41__static_initialization_and_destruction_0ii
    這個 function, 請問這有何作用呢?
    讓 c++ runtime 呼叫用的嗎?
    從 objdump 無法看出是誰 call _Z41__static_initialization_and_destruction_0ii。

    冒昧請教, 打擾了。

  • descent

    很感謝, 我不是做 linker, 我打算用 c++ 來練習寫 os。
    目前遭遇到 global/static object ctor/dtor c++ runtime 的問題。
    local object ctor/dtor 可以正常運作, 現在想把
    global/static object ctor/dtor 搞定。看了一些資料, 還不是很熟悉。

    不過我知道是誰 call _Z41__static_initialization_and_destruction_0ii
    是 global_ctor
    我之前的 objdump 下錯指令了。

    global object ctor 有點概念了,
    global object dtor 還不是很清楚。
    在 g++ static object 似乎還要處理 guard, 我想先搞定 global ojbect ctor/dtor。

  • Pingback: arm assembly example -部落格熱搜- arm assembly example()