1. 程式人生 > >GCC原始碼分析(四)——優化

GCC原始碼分析(四)——優化

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


一、前言

本篇只介紹一下框架,就不具體介紹每個步驟了。


二、Pass框架

上一篇已經講了gcc的中間語言的表現形式。gcc 對中間語言的每一步處理叫做一個pass。從一個函式的GENERIC樹剛被轉換為GIMPLE之後,接下來的工作就由一連串的pass來完成。這些pass環環相扣,最終完成整個程式的優化工作,為目的碼生成做最後的準備。

GCC的pass結構定義在gcc/tree-pass.h標頭檔案中:


[cpp]
 view plaincopy
  1. /* Optimization pass type.  */  
  2. enum opt_pass_type // 四種pass型別對應的列舉  
  3. {  
  4.   GIMPLE_PASS,  
  5.   RTL_PASS,  
  6.   SIMPLE_IPA_PASS,  
  7.   IPA_PASS  
  8. };  
  9.   
  10. /* Describe one pass; this is the common part shared across different pass 
  11.    types.  */  
  12. struct opt_pass 
    // pass的基本結構  
  13. {  
  14.   /* Optimization pass type.  */  
  15.   enum opt_pass_type type;  
  16.   
  17.   /* Terse name of the pass used as a fragment of the dump file 
  18.      name.  If the name starts with a star, no dump happens. */  
  19.   const char *name; // pass名字  
  20.   
  21.   /* If non-null, this pass and all sub-passes are executed only if 
  22.      the function returns true.  */  
  23.   bool (*gate) (void); // 是否應該執行此pass?  
  24.   
  25.   /* This is the code to run.  If null, then there should be sub-passes 
  26.      otherwise this pass does nothing.  The return value contains 
  27.      TODOs to execute in addition to those in TODO_flags_finish.   */  
  28.   unsigned int (*execute) (void); // 執行此pass!  
  29.   
  30.   /* A list of sub-passes to run, dependent on gate predicate.  */  
  31.   struct opt_pass *sub; // 子pass。如果此pass被關閉,子pass也被一起關閉。  
  32.   
  33.   /* Next in the list of passes to run, independent of gate predicate.  */  
  34.   struct opt_pass *next; // 後面的pass。  
  35.   
  36.   /* Static pass number, used as a fragment of the dump file name.  */  
  37.   int static_pass_number; // 一個唯一的pass號  
  38.   
  39.   /* The timevar id associated with this pass.  */  
  40.   /* ??? Ideally would be dynamically assigned.  */  
  41.   timevar_id_t tv_id; // 一個唯一的ID。  
  42.   
  43.   /* Sets of properties input and output from this pass.  */  
  44.   unsigned int properties_required; // 這些是要被檢查的property  
  45.   unsigned int properties_provided;  
  46.   unsigned int properties_destroyed;  
  47.   
  48.   /* Flags indicating common sets things to do before and after.  */  
  49.   unsigned int todo_flags_start;  // 這些是在執行此pass之前/之後的附加動作  
  50.   unsigned int todo_flags_finish;  
  51. };  
opt_pass的成員都很好理解,重點就在gate和execute兩個函式。這兩個函式都沒有引數。如果該pass是用來處理函式,引數通過全域性變數 current_function_decl 和 cfun 傳入,前者是當前function的GENERIC樹節點;後者是一個function結構體,裡面包含了這個函式相關的全部資訊(控制流,GENERIC,GIMPLE,等等)。

之後用繼承的方式定義了四個pass的子型別:

[cpp] view plaincopy
  1. struct gimple_opt_pass // gimple pass  
  2. {  
  3.   struct opt_pass pass;  
  4. };  
  5. struct rtl_opt_pass // rtl pass  
  6. {  
  7.   struct opt_pass pass;  
  8. };  
  9. struct ipa_opt_pass_d // ipa pass  
  10. {  
  11.   struct opt_pass pass;  
  12.   
  13.   /* IPA passes can analyze function body and variable initializers 
  14.       using this hook and produce summary.  */  
  15.   void (*generate_summary) (void); // 分析函式體和全域性變數的初始化過程  
  16.   
  17.   /* This hook is used to serialize IPA summaries on disk.  */  
  18.   void (*write_summary) (struct cgraph_node_set_def *); // 將ipa summary寫入磁碟  
  19.   
  20.   /* For most ipa passes, the information can only be deserialized in 
  21.      one chunk.  However, function bodies are read function at a time 
  22.      as needed so both calls are necessary.  */  
  23.   void (*read_summary) (void);  // 從磁碟讀取ipa summary,下同  
  24.   void (*function_read_summary) (struct cgraph_node *);  
  25.   /* Hook to convert gimple stmt uids into true gimple statements.  The second 
  26.      parameter is an array of statements indexed by their uid. */  
  27.   void (*stmt_fixup) (struct cgraph_node *, gimple *); // 語句修復  
  28.   
  29.   /* Results of interprocedural propagation of an IPA pass is applied to 
  30.      function body via this hook.  */  
  31.   unsigned int function_transform_todo_flags_start;  
  32.   unsigned int (*function_transform) (struct cgraph_node *); // 函式變形  
  33.   void (*variable_transform) (struct varpool_node *); // 變數變形  
  34. };  
  35.   
  36. struct simple_ipa_opt_pass // simple ipa pass  
  37. {  
  38.   struct opt_pass pass;  
  39. };  

這四種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
  1. /* The root of the compilation pass tree, once constructed.  */  
  2. extern struct opt_pass *all_passes, *all_small_ipa_passes, *all_lowering_passes,  
  3.                        *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就進入了最後的階段:目的碼生成。敬請期待下篇。