GCC原始碼分析(四)——優化
原文連結:http://blog.csdn.net/sonicling/article/details/7916931
一、前言
本篇只介紹一下框架,就不具體介紹每個步驟了。
二、Pass框架
上一篇已經講了gcc的中間語言的表現形式。gcc 對中間語言的每一步處理叫做一個pass。從一個函式的GENERIC樹剛被轉換為GIMPLE之後,接下來的工作就由一連串的pass來完成。這些pass環環相扣,最終完成整個程式的優化工作,為目的碼生成做最後的準備。
GCC的pass結構定義在gcc/tree-pass.h標頭檔案中:
- /* Optimization pass type. */
- enum opt_pass_type // 四種pass型別對應的列舉
- {
- GIMPLE_PASS,
- RTL_PASS,
- SIMPLE_IPA_PASS,
- IPA_PASS
- };
- /* Describe one pass; this is the common part shared across different pass
- types. */
- struct opt_pass
- {
- /* Optimization pass type. */
- enum opt_pass_type type;
- /* Terse name of the pass used as a fragment of the dump file
- name. If the name starts with a star, no dump happens. */
- const char *name; // pass名字
- /* If non-null, this pass and all sub-passes are executed only if
- the function returns true. */
- bool (*gate) (void); // 是否應該執行此pass?
- /* This is the code to run. If null, then there should be sub-passes
- otherwise this pass does nothing. The return value contains
- TODOs to execute in addition to those in TODO_flags_finish. */
- unsigned int (*execute) (void); // 執行此pass!
- /* A list of sub-passes to run, dependent on gate predicate. */
- struct opt_pass *sub; // 子pass。如果此pass被關閉,子pass也被一起關閉。
- /* Next in the list of passes to run, independent of gate predicate. */
- struct opt_pass *next; // 後面的pass。
- /* Static pass number, used as a fragment of the dump file name. */
- int static_pass_number; // 一個唯一的pass號
- /* The timevar id associated with this pass. */
- /* ??? Ideally would be dynamically assigned. */
- timevar_id_t tv_id; // 一個唯一的ID。
- /* Sets of properties input and output from this pass. */
- unsigned int properties_required; // 這些是要被檢查的property
- unsigned int properties_provided;
- unsigned int properties_destroyed;
- /* Flags indicating common sets things to do before and after. */
- unsigned int todo_flags_start; // 這些是在執行此pass之前/之後的附加動作
- unsigned int todo_flags_finish;
- };
之後用繼承的方式定義了四個pass的子型別:
- struct gimple_opt_pass // gimple pass
- {
- struct opt_pass pass;
- };
- struct rtl_opt_pass // rtl pass
- {
- struct opt_pass pass;
- };
- struct ipa_opt_pass_d // ipa pass
- {
- struct opt_pass pass;
- /* IPA passes can analyze function body and variable initializers
- using this hook and produce summary. */
- void (*generate_summary) (void); // 分析函式體和全域性變數的初始化過程
- /* This hook is used to serialize IPA summaries on disk. */
- void (*write_summary) (struct cgraph_node_set_def *); // 將ipa summary寫入磁碟
- /* For most ipa passes, the information can only be deserialized in
- one chunk. However, function bodies are read function at a time
- as needed so both calls are necessary. */
- void (*read_summary) (void); // 從磁碟讀取ipa summary,下同
- void (*function_read_summary) (struct cgraph_node *);
- /* Hook to convert gimple stmt uids into true gimple statements. The second
- parameter is an array of statements indexed by their uid. */
- void (*stmt_fixup) (struct cgraph_node *, gimple *); // 語句修復
- /* Results of interprocedural propagation of an IPA pass is applied to
- function body via this hook. */
- unsigned int function_transform_todo_flags_start;
- unsigned int (*function_transform) (struct cgraph_node *); // 函式變形
- void (*variable_transform) (struct varpool_node *); // 變數變形
- };
- struct simple_ipa_opt_pass // simple ipa pass
- {
- struct opt_pass pass;
- };
這四種pass,只有ipa pass比較特殊,其他的除了型別不一樣之外,其餘都一樣。當然,在初始化對應結構體時,opt_pass::type必須用對應的列舉來初始化。最後一種ipa pass區別於simple ipa pass,叫做regular ipa pass,在後面進一步介紹。
大部分常用的pass都實現在gcc目錄下的某些檔案中,這些檔案的特點是聲明瞭一個全域性的xxx_pass結構體變數,而這些變數在tree-pass.h中用extern宣告一遍,並在passes.c中的 init_optimizations() 函式中串在一起。該函式通過使用NEXT_PASS()巨集,初始化了5串pass:
[cpp] view plaincopy
- /* The root of the compilation pass tree, once constructed. */
- extern struct opt_pass *all_passes, *all_small_ipa_passes, *all_lowering_passes,
- *all_regular_ipa_passes, *all_lto_gen_passes;
他們被呼叫的順序和被初始化的順序是一致的:all_lowering_passes -> all_small_ipa_passes -> all_regular_ipa_passes -> all_lto_gen_passes -> all_passes。他們所作的事情大致如下:
all_lowering_passes:降級GIMPLE,從GIMPLE生成控制流圖,內聯形參...
all_small_ipa_passes:行內函數(early inline),內聯形參,重建cgraph邊,重建函式屬性,建立SSA,複寫傳播(copy propagation),清理...
all_regular_ipa_passes:行內函數(inline),常量判定(pure const),Escape分析,程序間Point-to分析(IPA PTA)...
all_lto_gen_passes:Link time optimization...
all_passes:exception handling,GIMPLE優化(SSA優化,dead call,別名分析,if合併,迴圈優化等等),GIMPLE->RTL,RTL優化(複寫傳播,dead call,暫存器合併,條件跳轉優化等等)清理...
其中 all_regular_ipa_passes和all_lto_gen_passes都是regular ipa pass,all_small_ipa_passes就是simple ipa pass,all_lowering_passes都是gimple pass,all_passes是由gimple pass和rtl pass組成。
三、三大類Pass
3.1、GIMPLE Pass
所有的Lowering pass和前半部分優化都是gimple pass。gimple pass可以遍歷當前函式的全部gimple語句。Lowering pass中的控制流生成之前,gimple pass只能從cfun裡遍歷全部的gimple,因為此時它們還沒有被組織成控制流圖的形式,之後的gimple pass就可以使用FOR_EACH_BB這樣的巨集逐塊掃描gimple語句。
3.2、RTL Pass
RTL pass基本上是優化的後半部分。由於RTL有暫存器、字長等GIMPLE沒有的底層概念,因此屬於底層優化,側重點也不同。RTL pass也可以使用FOR_EACH_BB對控制流進行遍歷,但是用FOR_BB_INSNS對基本塊中的insn進行處理,而gimple pass是通過gsi_start_bb來獲取gimple語句列表。
3.3、IPA Pass
IPA的全稱是Inter-Procedural Analysis。在gcc裡它有兩層意思:一個是跨函式的分析,一個是全域性變數(夾在函式間的變數)的分析。IPA所使用的工具是cgraph(call graph,呼叫圖)。呼叫圖記錄了函式之間的呼叫關係。程序間分析的重點就是函式間的變數傳遞(引數)和依賴關係(全域性變數,呼叫關係)。
IPA pass在每次執行時也只是針對一個函式,因此它在執行時也可訪問 current_function_decl 和 cfun,並通過它們獲取對應的cgraph_node,由此可以得到當前函式與cgraph裡的其他函式之間的關係。與此同時,gcc將全域性變數存放在varpool_nodes裡,這也是cgraph的一部分。
四、Pass的執行
Pass被Pass管理器執行。執行每一個pass的程式碼實現在gcc/passes.c裡。
4.1 幾個特殊的pass
在gcc/tree-optimize.c中定義了幾個特殊的pass,他們的作用如下:
pass_all_optimizations :它是程序內優化pass的第一個pass,也是他們的父pass。它只有gate函式,只做開關之用。
pass_early_local_passes:它是all_small_ipa_passes的後半部分,屬於IPA優化部分。它是IPA優化的開關。
pass_all_early_optimizations:它是pass_early_local_passes的一部分,除了做開關,還負責更新cgraph_state。cgraph允許任意時刻新增函式,但是如果在pass的後段新增函式,而這個函式沒有被之前的pass處理過,那就有問題,因此cgraph會根據當前的狀態來決定是否要對這個函式追加執行之前的pass。
pass_cleanup_cfg,pass_cleanup_cfg_post_optimizing和pass_fixup_cfg:在不同階段清理控制流圖,它是獨立的pass,沒有gate(預設執行)。
pass_init_datastructures:初始化所有SSA結構,為轉換SSA做準備。
4.2 Pass的執行順序
上一篇大致講到了Lowering在Optimization之前。在這裡,我詳細列出他們的呼叫關係:
cgraph_finalize_compilation_unit()
cgraph_analyze_functions()
cgraph_analyze_function()
gimplify_function_tree() -> gimplification。
cgraph_lower_function() -> lowering
cgraph_optimize()
ipa_passes()
if (!in_lto_p) execute_ipa_pass_list (all_small_ipa_passes); -> small IPA execute
if (!in_lto_p) execute_ipa_summary_passes(all_regular_ipa_passes) -> regular IPA summary
execute_ipa_summary_passes (all_lto_gen_passes); -> lto summary
if (!flag_ltrans) execute_ipa_pass_list (all_regular_ipa_passes); -> regular IPA (include LTO) execute
cgraph_expand_all_functions()
cgraph_expand_all_function()
tree_rest_of_compilation()
execute_all_ipa_transforms() -> regular IPA transform (include LTO) transform
execute_pass_list (all_passes) -> 程序內優化
4.3 普通Pass的執行
普通的pass由gcc/passes.c中的execute_one_pass()函式來負責呼叫。該函式的程式碼就不貼了,具體來說,它是這麼來呼叫每個普通pass的:
1. 檢查gate:gate_status = (pass->gate == NULL) ? true : pass->gate();
2. plugin複查gate:invoke_plugin_callbacks (PLUGIN_OVERRIDE_GATE, &gate_status);
3. 如果不需要執行,就退出。
4. 通知plugin準備execute:invoke_plugin_callbacks (PLUGIN_PASS_EXECUTION, pass);
5. 執行pass預定的TODO list:execute_todo (pass->todo_flags_start);
6. 檢查函式的property是否和pass的相符:do_per_function (verify_curr_properties, (void *)(size_t)pass->properties_required);
7. 執行pass:todo_after = pass->execute ();
8. 執行pass指定的結束TODO list:execute_todo (todo_after | pass->todo_flags_finish);
9. 如果是regular IPA Pass,記錄該pass到當前函式的IPA Transform列表中。
還有一些debug用的dumpfile操作就不提了。
4.3 Regular IPA Pass的執行
執行Regular IPA Pass的函式就不再作詳細介紹了,他們的執行流程被分散為3輪,每一輪的步驟都差不多,而且比普通pass的執行過程簡單。
每個IPA pass都有三次機會來執行。generate_summaries() -> execute() -> function_transform()。前兩次機會基本都差不多,都是用來掃描和準備引數,最後一次機會就是對cgraph實施改變。比如pass_ipa_inline,在generate_summaries裡面計算所有函式的大小,在execute裡面根據大小和其他資訊來判定哪些函式可以內聯,在transform裡面對所有標記為內聯的函式進行內聯,並更新cgraph。
儘管如此,generate_summaries() 和 execute() 還是有作用上的區別。由於前者先執行,而後者是否執行被gate()控制,並且transform是按照每個函式上掛載的ipa_pass 列表來執行,如果execute不執行的話,該pass也不會被掛載到當前函式上,因此 generate_summaries() 可以用來通過 gate() 控制後兩者是否被執行。
五、之後
執行完所有Pass之後,gcc就進入了最後的階段:目的碼生成。敬請期待下篇。