2012年10月8日 星期一

建置與安裝 LLVM + Clang

這篇文章教你怎麼從 LLVM trunk 下載 Source code, 並且建置與安裝 LLVM + Clang




下載程式碼

git clone http://llvm.org/git/llvm.git llvm
cd llvm/tools
git clone http://llvm.org/git/clang.git
cd ../../


建置 llvm

有興趣的話 configure 可以加個 --enable-cxx11
--enable-optimized 則是會用 -O2 來建置
mkdir build-llvm
cd build-llvm
../llvm/configure --prefix=$HOME --enable-optimized
make -j8
make install


將 llvm 加入 path

將下面那一行加到 .bashrc 中
export PATH=$HOME/bin:$PATH
然後重新開啟一個 console/terminal 以利重新載入環境變數 接著檢查一下路徑看看是不是正確的
which clang
預期應該是會輸出
~/bin/clang

接著執行看看吧!

clang -v
應該是會跟你吐類似下面的訊息, 在不同電腦上可能會有一點不同
clang version 3.2 
Target: x86_64-unknown-linux-gnu
Thread model: posix
clang 的選項大致上跟 gcc 很類似, 可以 --help 看一下選項
clang --help

使用 clang 編譯

clang hello.c -o hello

使用 clang 編譯成 bitcode

clang hello.c -o hello.bc -c -emit-llvm

使用 clang 編譯成 bitcode, 並且使用 lli 執行

clang hello.c -o hello.bc -c -emit-llvm
lli hello.bc


把玩 LLVM

[1] LLVM 寫一個 pass - 入門篇
[2] Writing an LLVM Pass

LLVM 寫一個 pass - 教學入門篇

注意!! 這篇文章並非官方的 Writing an LLVM Pass 的中文翻譯版本!!


而是精簡版, 採用建置方式是將 pass 的程式碼獨立於 LLVM 的 Source tree 外
這樣的好處是可避免與整串 LLVM 搞在一起, 並且對於整體建置流程會較為清楚

這篇文章對於讀者有些假設 :
  1. 已經會建置 LLVM (不會? 建置與安裝 LLVM + Clang)
  2. 對於 Makefile 有一點基礎知識 (沒有? 快來惡補)


1. 先從 LLVM 抄一個 Hello Pass

#define DEBUG_TYPE "hello"
#include "llvm/Pass.h"
#include "llvm/IR/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");
將上列程式碼儲存成 Hello.cpp 或著任何你喜歡的檔名
原程式碼上面有一些授權宣告跟註解, 基於文章版面配置將那部份移除, 其詳細授權見LLVM Developer Policy
說明?能建置成功前先把細節拋一邊吧

2. 建置 Makefile

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
把上面內容抄到與 Hello.cpp 同目錄下的 Makefile 中
注意一下 Makefile 建置動作前面的是 Tab 字元不是空白字元, 相當重要
這邊唯一要注意的是 LLVM_CONFIG 要設定為 你安裝的 llvm 路徑裡面的那個 llvm-config
例如 LLVM 裝在家目錄底下, 那麼 LLVM_CONFIG 就設定為 /home/kito/bin/llvm-config

3. 建置與測試

接著執行 make,
make
應該會吐出下面兩行
`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
然後目錄下應該會出現 Hello.so 才對
ls
# Hello.cpp  Hello.o  hello.so  Makefile
接著寫個 Hello World 的小程式
#include <stdio.h>

int main () {
  printf("Hello LLVM!\n");
}
將以上簡單內容存檔成 HelloWorld.c 接著把 Hello World 產生 .bc 檔, 加上 -emit-llvm 告訴 llvm 你要產生 bitcode
clang -c -emit-llvm -o HelloWorld.bc HelloWorld.c
最後步驟就是把你的 HelloWorld.bc 丟到前面寫的 Hello Pass !
opt -load ./hello.so -hello HelloWorld.bc -o /dev/null
後面 -o /dev/null 的意思是叫 opt 把經過最佳化或分析的結果直接丟掉
執行完後 LLVM 就會跟你說 Hello 了!
Hello: main
main 是你 Hello World 中的 Function 名稱

4. 回頭看一下 Hello Pass

DEBUG_TYPE 是 LLVM 拿來統計該 pass 被呼叫幾次或著是使用 DEBUG 輸出除錯訊息的時候用的
#define DEBUG_TYPE "hello"
include 要用到的 LLVM 的
#include "llvm/Pass.h"
#include "llvm/IR/Function.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/ADT/Statistic.h"
把 llvm namespace 的東西都弄進來, 雖然這是不好的習慣, 不過由於方便起見直接 using 下去吧XD
using namespace llvm;
匿名 namespace, 避免 export 裡面的 symbol,
聽無上一句在講啥就先跳過就可以了
namespace {
Hello 繼承 FunctionPass, 代表 Hello Pass 的輸入是以 Function 為單位
  // Hello - The first implementation, without getAnalysisUsage.
  struct Hello : public FunctionPass {
現階段下面這行就照抄吧
給 LLVM 用來識別這個 Pass 的 id
    static char ID; // Pass identification, replacement for typeid
Constructor
    Hello() : FunctionPass(ID) {}
這邊則是整個 pass 的精華所在, LLVM 會以一次一個 Function 為單位, 餵進來給你玩
大致上裡面的行為就是將餵進來的 Function 的名稱印出來而已
其中 return false 是告訴 LLVM 你沒有修改任何東西
    virtual bool runOnFunction(Function &F) {
      errs() << "Hello: ";
      errs() << F.getName()) << '\n';
      return false;
    }
class 跟 namespace 的結束, 沒啥好說的
  };
}
定義下一下 Hello::ID 的實體
char Hello::ID = 0;
跟 LLVM 註冊你的這個 Pass, 第一個參數是 pass name, 第二個則是說明用的文字
static RegisterPass<hello> X("hello", "Hello World Pass");

5. 解析 Makefile

先指定 llvm-config 執行檔的所在位置, 如果 llvm-config 已經加到 PATH 中的話那就填 llvm-config 就好
LLVM_CONFIG=llvm-config
llvm-config 這隻程式可以幫你產生出大部分你所需的 flag
用 --help 可以檢視所有可用選項
llvm-config --help
指定編譯器使用 clang, 單純個人喜好XD
CXX=`$(LLVM_CONFIG) --bindir`/clang
指定編譯的參數, 大部分從 llvm-config --cppflags 即可,
但由於我們現在是要編譯的是 Shared Library 所以要加 -fPIC
不知道 Shared Library 是啥? 快去弄一本程式設計師的自我修養來 K 阿~~
-fno-rtti 則是指定不產生 Run-time Typeinfo 的資訊,加了此選項後 typeid 及 dynamic_cast 會無法使用,基於實作成本 LLVM 在這方面有實作自己一套的 RTTI 機制
CXXFLAGS=`$(LLVM_CONFIG) --cppflags` -fPIC -fno-rtti
指定 Link 的參數, 大部分從 llvm-config --ldflags 即可,
現在是要編譯的是 Shared Library 所以加 -shared
LDFLAGS=`$(LLVM_CONFIG) --ldflags` -shared 
我們總共要建置 hello.so
all: hello.so
hello.so 怎麼生勒?, 生之前要先生出 Hello.o
然後下面一行則是建置規則
hello.so: Hello.o
        $(CXX) Hello.o -o hello.so $(LDFLAGS)
Hello.o 的建置規則, 以及其相依 Hello.cpp
Hello.o: Hello.cpp
        $(CXX) -c Hello.cpp -o Hello.o $(CXXFLAGS)
寫 Makefile 時寫個 clean 的 rule 是個好習慣 :)
clean:
        rm -f *.o hello.so

參考

[1] Writing an LLVM Pass
[2] 程式設計師的自我修養-連結、載入、程式庫

Update Note

  1. (2012/11/16) 在較新版的 llvm-config 無提供 -fno-rtti 選項,必須自行在 CXXFLAGS 那邊自行添加
  2. (201211/22) 上面提到那個項目目前不確定是 bug 還是怎樣,cmake 出來的 llvm-config 不會有 -fno-rtti,但 configure 建置出來的 llvm-config 則會有
  3. (2018/10/18) 更新範例中的 include path, 已測試 於 LLVM 6 及 7

2012年9月19日 星期三

C++11 : Strongly typed enumerations (enum class)

回顧 enum

在 C++11 以前以下 enum 會污染整個 namespace

大致的意思是如果你宣告了一個 enum E, 他有 A, B, C 三個值

enum E {
  A,  
  B,  
  C,  
};

那麼在這個 namespace 底下你就不能先告任何以 A, B, C 為 ID 型別或變數
enum E {
  A,  
  B,  
  C,  
};

int A; /* Oop, compiler 會跟你抱怨 A 這個符號重複定義了!! */

而且很不近乎人情的你想要使用 E::A 的方式取值還會跟你抱怨 E 不是一個 namespace 或 class
(在 VC++ 中有 extension 讓你可以這樣取)

C++11 前的折衷作法

這種情況事實上也不是不能解, 只要在 enum 外面包一層 namespace 即可, 但看起來會有點鳥就是了
namespace E {
  enum Type {
    A,  
    B,  
    C,  
  };
}

int A; /* A 這個 ID 可以用了! */

int var = E::B; /* 取用方式也以用 E:: 的方式取用 */

E::Type e = E::A; /* 最大缺點就是 Type name 會變成 E::Type */

enum 的型別問題

前一段包一層 namespace 的作法可以解決大部分的問題,

但是事實上 enum 還有一個問題就是, enum 常被拿來定義常數,

但是他實際的型別不明!!

在標準中也沒有規範 enum 要占多少大小

所以在大部分實作中, 大部分會以 int 來處理

在必要的時候他會長大成 64-bit 的 long long

#include <stdio.h>

enum E1 {
  A = 213,
  B,
  C,
};

enum E2 {
  D = 21343245325435ll,
  E,
  F,
};

int main(){
  printf("%zd\n", sizeof(enum E1)); /* 印出 4 */
  printf("%zd\n", sizeof(enum E2)); /* 印出 8 */
  return 0;
}
/* 註: 環境是 Fedora 17 x86-64
       編譯器為 g++ 4.6.3 以及 clang++  3.2 */

enum 的型別問題在哪?

enum E {
  A = 0x0ea92d6f << 4,
  B,
  C,
};

int main(){
  unsigned v = A;
  switch (v) {
    case A: /* gcc 4.6.3 可以 通過, clang 會跟你抱怨 - 359475472 沒辦法降轉到 unsigned! */
      break;
    default:
      break;
  }
  return 0;
}

/* 註: 環境是 Fedora 17 x86-64
       編譯器為 g++ 4.6.3 以及 clang++  3.2 */

enum E {
  A = 0x0ea92d6fu << 4, /*這邊加個 suffix u 就可解決 */
  B,
  C,
};

int main(){
  unsigned v = A;
  switch (v) {
    case A:
      break;
    default:
      break;
  }
  return 0;
}

/* 註: 環境是 Fedora 17 x86-64
       編譯器為 g++ 4.6.3 以及 clang++  3.2 */
當然第一眼看到可能會覺得這是編譯器實作問題,

但是事實上最核心的問題是

enum 的型別是啥

個人認為這等於在整個型別檢查上開了個漏洞...雖然 C 的 Type system 本來就很薄弱

C++ 則是不幸的承受了 C 的一切

Strongly typed enumerations

C++11 中目前加入了 Strongly typed enumerations 以及一些語法上的改進

解決了上列所提到的問題點

ENUM::VAL

enum E {
  A = 0x0ea92d6f,
  B,
  C,
};

int main(){
  int v1 = E::A; /* Ohhh 現在這是合法的使用方式了 */
  int v2 = A; /* 舊有取值方式向下相容 */
  return v;
}

型別問題

enum E : long long { /* 可以明確指定底層使用型別! */
  A = 123,
  B,
  C,
};

int main() {
  printf("%zd\n", sizeof(E)); /* 8 ! */
  return 0;
}

Strong Type

enum 除了可以拿來當常數宣告外,

在只拿來當列舉值時, 你可能就不會喜歡他能跟類 int 型別互轉不用錢的特性了
enum class E { /* 加個 class enum 換成 Strong Type! */
  A = 123,
  B,
  C,
};

int main() {
  int val = E::A; /* 跟你抱怨不能從 E 強轉成 int */
  return 0;
}

接著他也可以跟前面指定形別的語法結合
enum class E : long long { /* 加個 class enum 換成 Strong Type!
                             並且指定為 long long */
  A = 123,
  B,
  C,
};


最後要注意一點的是雖然他加了個 class, 不過他依然只是 enum, 不能有 member data 及 member function

2012年9月3日 星期一

vim 顯示多餘空白( trailing whitespace )

目前找到最短最方便的 script :D

" High light unwanted spaces in end of line
highlight ExtraWhitespace ctermbg=darkred guibg=darkcyan
autocmd BufEnter * if &ft != 'help' | match ExtraWhitespace /\s\+$/ | endif
autocmd BufEnter * if &ft == 'help' | match none /\s\+$/ | endif
把上面幾行丟到 ~/.vimrc 即可

預覽

紅色部份就是多餘的空白/Tab 字元

2012年8月17日 星期五

typeid in c++11

在 C++11 裡面把 typeid 拔掉, 取而代之的是 decltype

所以如果 code 有到的話

目前過渡期解法就在 Compile Flag 加個 `-Dtypeof=decltype` 吧

範例:
g++ -std=c++0x -Dtypeof=decltype foo.cpp
g++ -std=c++11 -Dtypeof=decltype foo.cpp



但如果 compiler 跟你抱怨 rvalue 不能轉 T& 的話...就用下面這有點邪門的解法吧


首先先在 code 頂端或著某個一定會 include 的 header 檔加入以下的 code
template<class T> struct unref     { typedef T type; };
template<class T> struct unref<T&> { typedef T type; };

然後編譯選項換成下面這樣
g++ -std=c++0x -Dtypeof\(x\)="unref<decltype(x)>::type" foo.cpp
g++ -std=c++11 -Dtypeof\(x\)="unref<decltype(x)>::type" foo.cpp

c++ 真的是越來越難學易用了XD...

參考

How can I use decltype to get the type of a reference? (from Stackoverflow)

git diff 使用 GUI 工具 diffmerge

在 git 使用時 git diff 是個很常會使用指令

但 git diff 是在 console 底下, 在某些情況下可能會有些吃力


這時候如果有 GUI 工具可以輔助的話效果會不錯


上面的圖示就是使用 diffmerge 的畫面, 左邊那調紅色代表兩邊不一樣的地方
左邊是舊有的右邊則是目前的, 並且右邊的視窗可以編輯

設定

而要使用 diffmerge 當 difftool 的話輸入以下指令設定
git config --global diff.tool diffmerge
git config --global difftool.diffmerge.cmd 'diffmerge "$LOCAL" "$REMOTE"'
git config --global merge.tool diffmerge
git config --global mergetool.diffmerge.cmd 'diffmerge --merge --result="$MERGED" "$LOCAL" "$(if test -f "$BASE"; then echo "$BASE"; else echo "$LOCAL"; fi)" "$REMOTE"'
git config --global mergetool.diffmerge.trustExitCode true

而之後使用 git difftool 或 git mergetool 則可呼叫出 diffmerge!

參考資料

Using DiffMerge as your Git visual merge and diff tool

2012年8月15日 星期三

GCC : Control Flow Graph

趁著記憶新鮮的時候先來紀錄一下

每個 Compiler 裡面的重要基礎設施之一就是 Control Flow Graph, 也就是 Basic Block 及其 Edge, 而這邊大概介紹一下 GCC 內部的資料結構及走訪方式, 主要參考來源是 GCC Source Code 及 GCC Internal.
GCC 的 Basic Block 在實作上要比較注意的一點是 GIMPLE 及 RTL 都會使用同樣得資料結構來表示, 但 RTL 的部份照慣例一樣是 expand 完後才會有, 而且同一個時間點只會是 GIMPLE 或 RTL

詳細的程式碼則可到 gcc/basic-block.h 裡面撈

1. Basic Block

在整個 CFG 中有兩個特別的 Block 一個是 ENTRY_BLOCK, 一個是 EXIT_BLOCK 他們分別可由 ENTRY_BLOCK_PTR EXIT_BLOCK_PTR 取得

在 basic_block 這個 struct 中有 next_bb 及 prev_bb 兩個欄位, 但沒事請直接別碰這兩個傢伙, 要走訪所有的 Basic Block 或要找兒子/父親的話下面有一些範例程式碼

走訪所有 Basic Block

basic_block bb;

FOR_EACH_BB (bb) {
  /* do something */
}

要注意的一點是有個東西叫 FOR_ALL_BB, 差別在於他會走到 ENTRY_BLOCK 跟 EXIT_BLOCK 而 FOR_EACH_BB 不會
basic_block bb;

FOR_ALL_BB (bb) {
  /* do something */
}

找父親

basic_block bb;
edge e;
edge_iterator ei;
FOR_EACH_EDGE(e, ei, bb->preds){
  /*
   * get src by `e->src`
   * get dest by `e->dest`
   */
}


找兒子


basic_block bb;
edge e;
edge_iterator ei;
FOR_EACH_EDGE(e, ei, bb->succs){
  /*
   * get src by `e->src`
   * get dest by `e->dest`
   */
}

走訪該 BB 的所有 RTL INSN

rtx insn;
basic_block bb;
FOR_BB_INSNS(bb, insn) {
  /* do something with insn */
}

走訪該 BB 的所有 GIMPLE stmt 及 PHI node


basic_block bb;
gimple_stmt_iterator si;

for (si = gsi_start_phis (bb); !gsi_end_p (si); gsi_next (&si)) {
  gimple phi = gsi_stmt (si);
  print_gimple_stmt (dump_file, phi, 0, TDF_SLIM);
}
for (si = gsi_start_bb (bb); !gsi_end_p (si); gsi_next (&si)) {
  gimple stmt = gsi_stmt (si);
  print_gimple_stmt (dump_file, stmt, 0, TDF_SLIM);
}


2. Edges

Edge flag 如下
以下是從 basic-block.h 那邊抽出來的, 跟 GCC Internal 描述的是不太一樣的東西
  1. EDGE_FALLTHRU : 這個 edge 不會產生額外的 jump, 代表 src/dest 之後一定會放在一起
  2. EDGE_ABNORMAL : Indirect jump 或著是 Exception Handling 的 Edge
  3. EDGE_ABNORMAL_CALL : 呼叫一個可能會發生 Exception 的函數或著是 No return 的函數(例如呼叫 exit 或 abort)
  4. EDGE_EH : 處理 Exception Handling 的 Edge
  5. EDGE_FAKE : 他是一個不是 edge 的 edge! 拿來給 profiling 時用的
  6. EDGE_DFS_BACK : 是一個 back edge
  7. EDGE_CAN_FALLTHRU : 可以弄成 EDGE_FALLTHRU
  8. EDGE_IRREDUCIBLE_LOOP : irreducible loop 裡面的 edge
  9. EDGE_SIBCALL : 摳到 sibcall
  10. EDGE_LOOP_EXIT : 迴圈的終點
  11. EDGE_TRUE_VALUE : 當 Conditional Branch 的 Condition 為 True 時的目的地
  12. EDGE_FALSE_VALUE : 當 Conditional Branch 的 Condition 為 False 時的目的地
  13. EDGE_EXECUTABLE : 作 SSA-CCP 最佳化的時候用的, 其他時候請不要亂用
  14. EDGE_CROSSING : Cold code 與 Hot code 交界點
  15. EDGE_PRESERVE : 這個 Edge 不准砍!!!
  16. EDGE_ALL_FLAGS : 代表所有 flag
  17. EDGE_COMPLEX : EDGE_ABNORMAL | EDGE_ABNORMAL_CALL | EDGE_EH | EDGE_PRESERVE, 很複雜就對了!