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 的大略運作方式.