而是精簡版, 採用建置方式是將 pass 的程式碼獨立於 LLVM 的 Source tree 外
這樣的好處是可避免與整串 LLVM 搞在一起, 並且對於整體建置流程會較為清楚
這篇文章對於讀者有些假設 :
- 已經會建置 LLVM (不會? 建置與安裝 LLVM + Clang)
- 對於 Makefile 有一點基礎知識 (沒有? 快來惡補)
1. 先從 LLVM 抄一個 Hello Pass
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
#define DEBUG_TYPE "hello"
#include "llvm/Pass.h"
#include "llvm/Function.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/ADT/Statistic.h"
using namespace llvm;
namespace {
// Hello - The first implementation, without getAnalysisUsage.
struct Hello : public FunctionPass {
static char ID; // Pass identification, replacement for typeid
Hello() : FunctionPass(ID) {}
virtual bool runOnFunction(Function &F) {
errs() << "Hello: " ;
errs() << F.getName() << '\n' ;
return false ;
}
};
}
char Hello::ID = 0;
static RegisterPass<Hello> X( "hello" , "Hello World Pass" );
|
原程式碼上面有一些授權宣告跟註解, 基於文章版面配置將那部份移除, 其詳細授權見LLVM Developer Policy
說明?能建置成功前先把細節拋一邊吧
2. 建置 Makefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
LLVM_CONFIG=llvm-config
CXX=`$(LLVM_CONFIG) --bindir` /clang
CXXFLAGS=`$(LLVM_CONFIG) --cppflags` -fPIC -fno-rtti
LDFLAGS=`$(LLVM_CONFIG) --ldflags`
all: hello.so
hello.so: Hello.o
$(CXX) -shared Hello.o -o hello.so $(LDFLAGS) -fPIC
Hello.o: Hello.cpp
$(CXX) -c Hello.cpp -o Hello.o $(CXXFLAGS)
clean:
rm -f *.o hello.so
|
注意一下 Makefile 建置動作前面的是 Tab 字元不是空白字元, 相當重要
這邊唯一要注意的是 LLVM_CONFIG 要設定為 你安裝的 llvm 路徑裡面的那個 llvm-config
例如 LLVM 裝在家目錄底下, 那麼 LLVM_CONFIG 就設定為 /home/kito/bin/llvm-config
3. 建置與測試
接著執行 make,
1
|
make
|
1
2
|
`llvm-config --bindir` /clang -c Hello.cpp -o Hello.o `llvm-config --cppflags` -fPIC
`llvm-config --bindir` /clang -shared Hello.o -o hello.so `llvm-config --ldflags` -fPIC
|
1
2
|
ls
# Hello.cpp Hello.o hello.so Makefile
|
1
2
3
4
5
|
#include <stdio.h>
int main () {
printf ( "Hello LLVM!\n" );
}
|
1
|
clang -c -emit-llvm -o HelloWorld. bc HelloWorld.c
|
1
|
opt -load . /hello .so -hello HelloWorld. bc -o /dev/null
|
執行完後 LLVM 就會跟你說 Hello 了!
1
|
Hello: main
|
4. 回頭看一下 Hello Pass
DEBUG_TYPE 是 LLVM 拿來統計該 pass 被呼叫幾次或著是使用 DEBUG 輸出除錯訊息的時候用的
1
|
#define DEBUG_TYPE "hello"
|
1
2
3
4
|
#include "llvm/Pass.h"
#include "llvm/Function.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/ADT/Statistic.h"
|
1
|
using namespace llvm;
|
聽無上一句在講啥就先跳過就可以了
1
|
namespace {
|
1
2
|
// Hello - The first implementation, without getAnalysisUsage.
struct Hello : public FunctionPass {
|
給 LLVM 用來識別這個 Pass 的 id
1
|
static char ID; // Pass identification, replacement for typeid
|
1
|
Hello() : FunctionPass(ID) {}
|
大致上裡面的行為就是將餵進來的 Function 的名稱印出來而已
其中 return false 是告訴 LLVM 你沒有修改任何東西
1
2
3
4
5
|
virtual bool runOnFunction(Function &F) {
errs() << "Hello: " ;
errs() << F.getName()) << '\n' ;
return false ;
}
|
1
2
|
};
}
|
1
|
char Hello::ID = 0;
|
1
|
static RegisterPass<hello> X( "hello" , "Hello World Pass" );
|
5. 解析 Makefile
先指定 llvm-config 執行檔的所在位置, 如果 llvm-config 已經加到 PATH 中的話那就填 llvm-config 就好
1
|
LLVM_CONFIG=llvm-config
|
用 --help 可以檢視所有可用選項
1
|
llvm-config --help
|
1
|
CXX=`$(LLVM_CONFIG) --bindir` /clang
|
但由於我們現在是要編譯的是 Shared Library 所以要加 -fPIC
不知道 Shared Library 是啥? 快去弄一本程式設計師的自我修養來 K 阿~~
-fno-rtti 則是指定不產生 Run-time Typeinfo 的資訊,加了此選項後 typeid 及 dynamic_cast 會無法使用,基於實作成本 LLVM 在這方面有實作自己一套的 RTTI 機制
1
|
CXXFLAGS=`$(LLVM_CONFIG) --cppflags` -fPIC -fno-rtti
|
現在是要編譯的是 Shared Library 所以加 -shared
1
|
LDFLAGS=`$(LLVM_CONFIG) --ldflags` -shared
|
1
|
all: hello.so
|
然後下面一行則是建置規則
1
2
|
hello.so: Hello.o
$(CXX) Hello.o -o hello.so $(LDFLAGS)
|
1
2
|
Hello.o: Hello.cpp
$(CXX) -c Hello.cpp -o Hello.o $(CXXFLAGS)
|
1
2
|
clean:
rm -f *.o hello.so
|
參考
[1] Writing an LLVM Pass[2] 程式設計師的自我修養-連結、載入、程式庫
Update Note
- (11/16) 在較新版的 llvm-config 無提供 -fno-rtti 選項,必須自行在 CXXFLAGS 那邊自行添加
- (11/22) 上面提到那個項目目前不確定是 bug 還是怎樣,cmake 出來的 llvm-config 不會有 -fno-rtti,但 configure 建置出來的 llvm-config 則會有