原文链接:http://blog.youkuaiyun.com/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 // 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:
- /* 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就进入了最后的阶段:目标代码生成。敬请期待下篇。