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
- enum tree_code_class {
- tcc_exceptional, /* An exceptional code (fits no category). */
- tcc_constant, /* A constant. */
- /* Order of tcc_type and tcc_declaration is important. */
- tcc_type, /* A type object code. */
- tcc_declaration, /* A declaration (also serving as variable refs). */
- tcc_reference, /* A reference to storage. */
- tcc_comparison, /* A comparison expression. */
- tcc_unary, /* A unary arithmetic expression. */
- tcc_binary, /* A binary arithmetic expression. */
- tcc_statement, /* A statement expression, which have side effects
- but usually no interesting value. */
- tcc_vl_exp, /* A function call or other expression with a
- variable-length operand vector. */
- tcc_expression /* Any other expression. */
- };
每個TREE除了有類別,還有自己的型別:
[cpp] view plaincopy
- enum tree_code {
- #include "all-tree.def"
- MAX_TREE_CODES
- };
這個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
- /* For iterating over basic blocks. */
- #define FOR_BB_BETWEEN(BB, FROM, TO, DIR) \
- for (BB = FROM; BB != TO; BB = BB->DIR)
- #define FOR_EACH_BB_FN(BB, FN) \
- FOR_BB_BETWEEN (BB, (FN)->cfg->x_entry_block_ptr->next_bb, (FN)->cfg->x_exit_block_ptr, next_bb) // for迴圈遍歷連結串列。
- #define FOR_EACH_BB(BB) FOR_EACH_BB_FN (BB, cfun) // cfun就是current_function_decl,是一個TREE
[cpp] view plaincopy
- #define FOR_EACH_EDGE(EDGE,ITER,EDGE_VEC) \ // 前兩個引數的型別分別是edge和edge_iterator,是出參
- for ((ITER) = ei_start ((EDGE_VEC)); \ // 最後一個是入參,要麼是bb->preds(入邊集合),要麼是bb->succs(出邊集合)
- ei_cond ((ITER), &(EDGE)); \
- ei_next (&(ITER)))
四、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
- /* This must also call cgraph_finalize_compilation_unit. */
- 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優化的框架將在下一篇介紹。