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, 很複雜就對了!

2012年6月28日 星期四

GCC RTL INSN 簡介

簡介


在 GCC Backend 中的 IR 主要使用 RTL , 當作一些 Target Dependency 的一些相關操作時常會需要直接的對 RTL 去做一些檢查或掃描, 而在這篇文章概略性的介紹了 RTL INSN 的種類及樣貌, 以供入門

RTL INSN



RTL INSN 所用的內部的結構為 Doubly-Linked Lists
由一個又一個的 INSN 所串列而成
其中 insn 分為下列幾種
  1. insn: 一般的指令, 非 branch/jump/call 的指令
  2. jump_insn: 跳躍指令, 會造成任何 control flow 轉移的指令類型, 包含 conditional/unconditional jump, 及 indirect jump.
  3. call_insn: 任何呼叫函數的指令
  4. debug_insn: 塞 debug info 的
  5. note: 就 note 阿
  6. barrier: 任何 control flow 都不應該穿越這傢伙, 隔絕各個 Basic Block 東東
  7. code_label: 在 c 裡面給 goto 用的 label 會變成這種東西
當然詳細的還是到 GCC Internal 會寫的比較清楚 http://gcc.gnu.org/onlinedocs/gccint/Insns.html
接著來看一下實際的 insn 長怎樣
(insn 21 20 22 6 
      (set (reg:SI 59 [ D.1238 ])
           (mem/c/i:SI (plus:SI (reg/f:SI 54 virtual-stack-vars)
                                (const_int -8 [0xfffffffffffffff8])) [0 sum+0 S4 A32]))
      /home/kito/sum.c:7 -1
      (nil))

大致上就是 Lisp-style 的樣子
在 gcc/rtl.def 檔案裡可以找到 insn 的定義 DEF_RTL_EXPR(INSN, "insn", "iuuBeiie", RTX_INSN)
由這串我們來解讀整個是在幹嘛的

攤開成一個一行來看, 並且參照上面定義 : "iuuBeiie", 一道 insn 總共由八個東西所組成
(insn 21
      20
      22
      6 
      (set (reg:SI 59 [ D.1238 ])
           (mem/c/i:SI (plus:SI (reg/f:SI 54 virtual-stack-vars)
                                (const_int -8 [0xfffffffffffffff8])) [0 sum+0 S4 A32]))
      /home/kito/sum.c:7
      -1
      (nil))


後面 # 後面為註解
(insn 21 # 1. i
      20 # 2. u
      22 # 3. u
      6  # 4. B
      (set (reg:SI 59 [ D.1238 ])
           (mem/c/i:SI (plus:SI (reg/f:SI 54 virtual-stack-vars)
                                (const_int -8 [0xfffffffffffffff8])) [0 sum+0 S4 A32])) # 5. e
      /home/kito/sum.c:7 # i
      -1 # 6. i
      (nil)) # 7. e
  1. i 代表該 INSN 的 uid , 也就是唯一的編號
  2. u 上一道 INSN 的 uid
  3. u 下一道 INSN 的 uid
  4. B Basic Block 的編號
  5. e 這道 INSN 的主要內容, 例如這上面那道指令所描述的是從記憶體讀取一個值到暫存器中
  6. i 此 INSN 相對應回原始碼的位置
  7. i 放 RTL pattern Name
  8. e 塞 REG_NOTES 資訊, 主要是暫存器相關資訊如 live or kill 等等

相關存取函數可以從 rtl.h 中找到
/* ACCESS MACROS for particular fields of insns.  */

/* Holds a unique number for each insn.
   These are not necessarily sequentially increasing.  */
#define INSN_UID(INSN)  XINT (INSN, 0)

/* Chain insns together in sequence.  */
#define PREV_INSN(INSN) XEXP (INSN, 1)
#define NEXT_INSN(INSN) XEXP (INSN, 2)

#define BLOCK_FOR_INSN(INSN) XBBDEF (INSN, 3)

/* The body of an insn.  */
#define PATTERN(INSN)   XEXP (INSN, 4)

#define INSN_LOCATOR(INSN) XINT (INSN, 5)

相關判斷函數也可以從 rtl.h 中找到
/* Predicate yielding nonzero iff X is a label insn.  */
#define LABEL_P(X) (GET_CODE (X) == CODE_LABEL)

/* Predicate yielding nonzero iff X is a jump insn.  */
#define JUMP_P(X) (GET_CODE (X) == JUMP_INSN)

/* Predicate yielding nonzero iff X is a call insn.  */
#define CALL_P(X) (GET_CODE (X) == CALL_INSN)

/* Predicate yielding nonzero iff X is an insn that cannot jump.  */
#define NONJUMP_INSN_P(X) (GET_CODE (X) == INSN)

/* Predicate yielding nonzero iff X is a debug note/insn.  */
#define DEBUG_INSN_P(X) (GET_CODE (X) == DEBUG_INSN)

/* Predicate yielding nonzero iff X is an insn that is not a debug insn.  */
#define NONDEBUG_INSN_P(X) (INSN_P (X) && !DEBUG_INSN_P (X))

/* Nonzero if DEBUG_INSN_P may possibly hold.  */
#define MAY_HAVE_DEBUG_INSNS MAY_HAVE_DEBUG_STMTS

/* Predicate yielding nonzero iff X is a real insn.  */
#define INSN_P(X) \
  (NONJUMP_INSN_P (X) || DEBUG_INSN_P (X) || JUMP_P (X) || CALL_P (X))

/* Predicate yielding nonzero iff X is a note insn.  */
#define NOTE_P(X) (GET_CODE (X) == NOTE)

/* Predicate yielding nonzero iff X is a barrier insn.  */
#define BARRIER_P(X) (GET_CODE (X) == BARRIER)
其中要注意的部份是 INSN_P 所濾出來的不是 INSN 而是 INSN, DEBUG_INSN, JUMP_INSN 及 CALL_INSN
要單純的 INSN 要用 NONJUMP_INSN_P 來判斷


小結


對於 GCC Backend 的 Porting 來說 RTL 相關部份是相當重要的基礎知識之一,
如果真的不幸的踏入 GCC 屠龍之道則相關文件一定要先熟讀幾次
以及 rtl.h 也一定要仔細的看一遍, 避免發生悲劇...

2012年6月3日 星期日

GCC : Predicates vs Constraints


順手先把一些東西紀錄一下

在 GCC 的 md 中, 寫那堆 define_insn, define_expand, ...等的 pattern 中

很重要的一環是要寫好 match_operand 中的 Predicates 跟 Constraints

例如以下拿 arm 的 movsi 的 pattern 來說

(define_insn "*arm_movsi_insn"
  [(set (match_operand:SI 0 "nonimmediate_operand" "=rk,r,r,r,rk,m")
       (match_operand:SI 1 "general_operand"      "rk, I,K,j,mi,rk"))]
  "TARGET_ARM && ! TARGET_IWMMXT
   && !(TARGET_HARD_FLOAT && TARGET_VFP)
   && (   register_operand (operands[0], SImode)
       || register_operand (operands[1], SImode))"
  "@
   mov%?\\t%0, %1
   mov%?\\t%0, %1
   mvn%?\\t%0, #%B1
   movw%?\\t%0, %1
   ldr%?\\t%0, %1"
  [(set_attr "type" "*,*,*,*,load1")
   (set_attr "insn" "mov,mov,mvn,mov,*")
   (set_attr "predicable" "yes")
   (set_attr "pool_range" "*,*,*,*,4096")
   (set_attr "neg_pool_range" "*,*,*,*,4084")]
)

其中 nonimmediate_operand 及 general_operand 是 Predicates 的部份

Constraints 則是後面那串 "=rk,r,r,r,rk,m" 及 "rk, I,K,j,mi,rk"

而這兩者之間的差別在於一個指定大類別, 一個小類別

可以想像成 Predicates 是大篩子, Constraints 是小篩子

因此在 Insn Pattern Match 時, GCC 會先透過大篩子

來分類每個 RTL Insn 會適用於哪個 Insn Pattern

然後再想辦法透過任何可能的方法讓他符合任何一個 Constraints

(其中方法包含 split, expand 或著是神奇的 reload

所以若要將 mov pattern 拆解成 mov, store, load 三個 pattern 的話

就必須將 Predicates 的部份作適度的調整,

則可避免  Insn Pattern Match 時統統掉入 mov pattern

值得注意的是, GCC 內部是會由上到下掃描各式 pattern,

所以若大篩子不夠嚴謹造成某個 RTL Insn 可 match 多個 pattern 時,

則會採用第一個 match 到的 pattern,

相對於 md 來說就是比較前面定義的 pattern 會先被 match 到

而以上部份就是大致 gcc 在 md 中的 Predicates 與 Constraints 的大略運作方式.