1. 程式人生 > >GCC原始碼分析(三)——中間語言

GCC原始碼分析(三)——中間語言

原文連結:http://blog.csdn.net/sonicling/article/details/7915301


一、前言

  很忙,很久沒更新部落格了,繼續沒寫完的gcc分析,爭取在傳說將要用C++重寫的gcc 5出來之前初略分析完。大笑

二、符號表(GENERIC)

  前篇介紹了gcc的語法分析,在語法分析過程中,所有識別出來的語言部件都用一個叫TREE的變數儲存著。這個TREE就是gcc語法樹,叫做GENERIC。實際上它也是gcc的符號表,因為變數名、型別等等這些資訊都由TREE關聯起來。

  GENERIC的節點都定義在gcc/tree.h標頭檔案裡。它將GENERIC按類別分為若干類:

[cpp] view plaincopy
  1. enum tree_code_class {  
  2.   tcc_exceptional, /* An exceptional code (fits no category).  */  
  3.   tcc_constant,    /* A constant.  */
      
  4.   /* Order of tcc_type and tcc_declaration is important.  */  
  5.   tcc_type,        /* A type object code.  */  
  6.   tcc_declaration, /* A declaration (also serving as variable refs).  */
      
  7.   tcc_reference,   /* A reference to storage.  */  
  8.   tcc_comparison,  /* A comparison expression.  */  
  9.   tcc_unary,       /* A unary arithmetic expression.  */  
  10.   tcc_binary,      /* A binary arithmetic expression.  */  
  11.   tcc_statement,   /* A statement expression, which have side effects 
  12.               but usually no interesting value.  */  
  13.   tcc_vl_exp,      /* A function call or other expression with a 
  14.               variable-length operand vector.  */  
  15.   tcc_expression   /* Any other expression.  */  
  16. };  

每個TREE除了有類別,還有自己的型別:

[cpp] view plaincopy
  1. enum tree_code {  
  2. #include "all-tree.def"  
  3. MAX_TREE_CODES  
  4. };  

這個all-tree.def是編譯期自動生成的檔案,主要來源於tree.def檔案,還包含一些其它語言特定的TREE型別。每個TREE變數代表一個節點。

  每個TREE變數 t 都可以通過 TREE_CODE_CLASS(t) 巨集獲取類別,或者通過TREE_CODE(t) 巨集獲取型別,由此知道這個TREE是指的啥。gcc/tree.h裡定義了絕大多數對TREE的操作(巨集和函式),比如獲取某個TREE的型別:TREE_TYPE(t),通過它可以獲取函式的原型、原型的返回值、指標或陣列的型別等等;還有const char *get_name(TREE t),獲取TREE的名字,如果這個TREE代表一個變數,那麼它就返回變數名。具體每種節點型別具有哪些屬性可以去查GCC Internals。

三、控制流圖(Control Flow Graph)

  每個函式翻譯為GENERIC的語法樹之後,會進行gimplification(gimple化,gimple在下節介紹),在這一過程中函式的語法樹被翻譯為了控制流圖的形式。每個函式對應一個控制流圖。

  控制流由基本塊(Basic Block)組成。每個基本塊具有一串指令序列,並且只能有一個入口和一個出口,因此在這個序列內部不允許存在跳轉。gcc對基本塊的操作主要定義在gcc/basic-block.h裡,比如常用的基本塊的遍歷:

[cpp] view plaincopy
  1. /* For iterating over basic blocks.  */  
  2. #define FOR_BB_BETWEEN(BB, FROM, TO, DIR) \  
  3.   for (BB = FROM; BB != TO; BB = BB->DIR)  
  4.   
  5. #define FOR_EACH_BB_FN(BB, FN) \  
  6.   FOR_BB_BETWEEN (BB, (FN)->cfg->x_entry_block_ptr->next_bb, (FN)->cfg->x_exit_block_ptr, next_bb) // for迴圈遍歷連結串列。  
  7.   
  8. #define FOR_EACH_BB(BB) FOR_EACH_BB_FN (BB, cfun) // cfun就是current_function_decl,是一個TREE  
basic block在控制流中以連結串列的形式存放,它們由edge組成邏輯意義上的圖。gcc提供了對每個基本塊相關的邊進行遍歷的巨集:

[cpp] view plaincopy
  1. #define FOR_EACH_EDGE(EDGE,ITER,EDGE_VEC)   \ // 前兩個引數的型別分別是edge和edge_iterator,是出參  
  2.   for ((ITER) = ei_start ((EDGE_VEC));      \ // 最後一個是入參,要麼是bb->preds(入邊集合),要麼是bb->succs(出邊集合)  
  3.        ei_cond ((ITER), &(EDGE));       \  
  4.        ei_next (&(ITER)))  
每個edge有flags標誌位,用來判別邊的型別,它決定了跳轉的方式(條件、無條件等等)

四、GIMPLE和RTL

  gimple和RTL是gcc用來表示指令的兩種形式。因此每個基本塊都包含有兩組指令序列,一組是gimple指令,一組是RTL指令。每個函式將首先被gimple化,此時基本塊裡只包含gimple指令,之後由gimple生成RTL。

  gimple是一種包含最多三個運算元的中間指令,也就是編譯原理裡講的四元碼(三個運算元,一個操作符),基本上也就是 dst = src1 @ src2 的這種形式。由於gimple最多隻能對兩個運算元進行計算,因此一個複雜的表示式會展開為一系列的gimple指令,這一過程就是gimple化。gimple化的程式碼實現在gcc/gimplify.c中,核心的思想就是對語法樹進行後序遍歷,對每個非葉子節點生成一條gimple指令,自動生成必要的中間變數,並正確識別出基本塊,從而生成完整的控制流。

  從原始碼來看,語法分析中,每分析完一個函式,就會呼叫finish_function,它又會呼叫cgraph_finalize_function將函式新增到cgraph裡,只有這個函式被呼叫才會繼續處理它。分析整個檔案後,compile_file()函式會呼叫一個hook:

[cpp] view plaincopy
  1. /* This must also call cgraph_finalize_compilation_unit.  */  
  2. lang_hooks.decls.final_write_globals ();  

這個hook實際上是write_global_declarations() (in gcc/langhooks.c),它會呼叫註釋中提到的 cgraph_finalize_compilation_unit() 函式,接下來就是這樣的呼叫關係:

write_global_declarations()
    cgraph_finalize_compilation_unit()
        cgraph_analyze_function()
            gimplify_function_tree() -> gimplification。
            cgraph_lower_function() -> lowering
        cgraph_optimize() -> 優化

  在所有針對gimple的優化完成後,有一個叫做pass_expand的步驟,它將gimple展開為RTL。RTL是一種相對底層的指令,如果說gimple的重點在於控制流和資料流這種邏輯結構的話,那麼RTL的重點就在資料和控制的精確描述。通過RTL可以將運算元的長度、對齊、操作的型別、副作用等資訊表述出來,從而有利於自動化地進行最後的指令生成。

  RTL的指令在gcc中稱之為insn,insn是有語法和語義的,它被gcc的生成工具所識別和處理,並生成對應的.c檔案作為gcc的一部分一同編譯到gcc的執行檔案中。這部分的細節在後序篇幅中再做介紹。

五、總結

  GENERIC、GIMPLE和RTL三者構成了gcc中間語言的全部,它們以GIMPLE為核心,由GENERIC承上,由RTL啟下,在原始檔和目標指令之間的鴻溝之上構建了一個三層的過渡。接下來,gcc的工作就是對中間語言進行平臺無關優化。有關gcc優化的框架將在下一篇介紹。