GCC源码分析(四)——优化

本文深入解析GCC编译器的优化流程,包括中间语言表现形式、优化框架、优化类型(GIMPLEPass、RTLPass、IPAPass)、执行顺序及关键Pass功能,以及最终的目标代码生成阶段。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文链接:http://blog.youkuaiyun.com/sonicling/article/details/7916931


一、前言

本篇只介绍一下框架,就不具体介绍每个步骤了。


二、Pass框架

上一篇已经讲了gcc的中间语言的表现形式。gcc 对中间语言的每一步处理叫做一个pass。从一个函数的GENERIC树刚被转换为GIMPLE之后,接下来的工作就由一连串的pass来完成。这些pass环环相扣,最终完成整个程序的优化工作,为目标代码生成做最后的准备。

GCC的pass结构定义在gcc/tree-pass.h头文件中:


  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的子类型:

  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:

  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就进入了最后的阶段:目标代码生成。敬请期待下篇。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值