* Tree SSA 优化框架分析
以下部分参考了2004年的"Design and Implementation of Tree SSA"一文。
** 相关的文件
在这里,我们将TreeSSA优化框架看作一个GCC的模块。这个模块的接口——所有的API
定义以及数据结构的定义——都在文件tree-flow.h和tree-pass.h中。前者定义了数据流
和控制流分析(树上的),而后者则定义了用于描述一个tree-ssa优化遍的接口和数据结构。
其他的相关文件可以根据不同的职责分类:
*** 基础设施
passes.c中的init_optimization_passes函数负责调度所有需要执行的遍,它在
toplev_main中的general_init函数的最后被调用。所有添加的遍都要在这个函数中注册。
tree-optimize.c是所有树优化遍的主要驱动。它包含函数tree_rest_of_compilation,
这个函数完成对于一个FNDECL树的所有的优化和编译工作,FNDECL是前端生成的。
tree-ssa.c实现了一些操作SSA形式的函数;tree-into-ssa.c将一个一般形式的程序
转化成SSA形式的;tree-outof-ssa.c将一个SSA形式的程序转换成一般形式的。
tree-ssanames.c 和tree-phinodes.c实现了对于SSA_NAME和PHI_NODE的操作和存储管
理。
tree-cfg.c包含创建和操作控制流图的代码。
tree-dfa.c包含处理数据流图的代码。
tree-ssa-operands.c包含操作ssa操作数的代码。
tree-iterator.[hc]包含操作GENERIC和GIMPLE树语句的迭代器。
gimplify.c将GENERIC转换成GIMPLE,tree-gimple.[hc]包含分析和验证GIMPLE树的代
码。
*** 转化遍
gimple-low.c将GIMPLE转化成无结构的形式,这个在优化之前做,简化优化器的工作。
tree-ssa-pre.c tree-ssa-dse.c tree-ssa-forwprop.c tree-ssa-dce.c
tree-ssa-ccp.c tree-sra.c tree-ssa-dom.c 实现了通常的标量优化。
tree-ssa-loop-im.c tree-ssa-loop-ivcanon.c tree-ssa-loop-ivopts.c
tree-ssa-loop-manip.c tree-ssa-loop-niter.c tree-loop-linear.c
tree-ssa-loop-prefetch.c tree-ssa-loop-unswitch.c tree-parloops.c
tree-ssa-loop.c tree-ssa-loop-ch.c完成循环相关的优化。
还有其他一些优化遍...
*** 分析遍
tree-ssa-alias.c 别名分析
以及其他一些分析遍...
** 如果一个语言的前端想要利用所有的树优化遍,它需要满足
1. 有function-as-tree的表示,即,每一个完整的function都使用一个树来表示;
2. 必要的话提供LANG_HOOKS_GIMPLIFY_EXPR的定义;
3. 调用gimplify_function_tree函数将GENERIC转化成GIMPLE;
4. 将生成的GIMPLE表示给tree_rest_of_compilation完成剩余的所有工作。
** PassManager
1. 每一个SSA遍都必须注册并被init_optimization_pass调度。
2. 每一个遍都是由结构tree_opt_pass描述的。
3. 增加一个新遍:
1. 创建一个全局的struct tree_opt_pass类型的变量,并在tree-passes.h中增加
它的一个外部声明。
2. 在passes.c的init_optimization_passes中用NEXT_PASS注册这个遍
** SSA 形式
大部分的树优化器都依赖于SSA表示。SSA同时提供了程序的数据流信息。
*** SSA的操作数有两种形式
real:用于表示一个单独的,没有与别人有别名关系的,可以用一个语句原子地更新的
内存位置。例如,一个非聚合类型的变量,并且没有取过它的地址。
virtual:虚操作数表示部分引用或是与别人有别名关系的引用。例如,结构体成员,
指针解引用等。虚操作数是一个符号,可能是创建的,也可能是从程序中符号倒出
的。
1. 对于每一个指针的解引用,会对应一个Memory Tag(MT),表示该指针指向的存
储位置。这个MT是新创建的符号。
2. 对于聚合类型成员的引用,对应的虚操作数是该聚合类型的基名字对应的符号
例如,a.b.c=3对应于对a的一个虚定义。虚定义还可能叫“may def”或是
“non-killing def”,类似的,虚使用也可能叫“may use”
*** 对于每一个树节点,如果谓词SSA_VAR_P为真,则表示该节点代表的变量需要使用SSA的
形式来表示。一般包括VAR_DECL,PARM_DECL,RESULT_DECL,MTAG_P。函数
is_gimple_reg可以用于判断一个SSA_VAR_P为真的变量应该用real还是virtual表示。
返回真表示用real表示。每一个语句都有四个数组用于描述它的操作数,分别是
DEF_OPS,USE_OPS,VDEF_OPS,VUSE_OPS。这些信息存储在一个辅助的数据结构中,这
个数据结构叫做语句注释struct stmt_ann_t。
*** SSA换名
我们用SSA_NAME树结点表示一个SSA表示的名字。SSA换名在tree-into-ssa.c中实现。
所有的real和virtual操作数都被包装在一个SSA_NAME节点中,只有定义以及虚定义可
以创建新的SSA_NAME节点(还有一种情况,PHI节点)。
在此,虚操作数需要额外的特殊处理。虚操作数的定义是(non-killing)的,所以,一
个虚操作数定义序列应该被关联起来,例如:
# a = VDEF
a[i] = f();
# a = VDEF
a[j] = g();
# a = VDEF
a[k] = h();
会被翻译成:
# a2 = VDEF
a[i] = f();
# a3 = VDEF
a[j] = g();
# a4 = VDEF
a[k] = h();
我们看到,每一个VDEF都好像一个“读——写”操作。正是这种依赖关系,使得某些标
量优化不会错误地移除或移动某些代码。这种表示实际上是"factored use-def(FUD)"
表示。如果某些优化想要处理虚操作数,那么一般需要建立额外的信息。当把程序表示
转换出SSA形式的时候,不必为虚操作数插入拷贝语句,只要简单地将虚操作数的表示
删除就可以了。
实操作数没有这些考虑。每一个SSA_NAME都被认为是不同的变量。当将程序转换出SSA
形式的时候,同一个变量的不同版本的overlaping的生存期通过创建一个新变量并插入
相应的拷贝命名来处理。
*** Alias analysis
别名信息是通过Memory Tag表现在SSA中的。在GIMPLE中,没有多于一层的指针,对于
每一个*p,都对应一个MT。例如,如果编译器发现某个指针p可能指向a和b,并且p被
解引用过,那么就会为p创建一个MT,并且把a、b加到这个MT中。如果一个变量的地址
被取,那么它总是被认为是一个虚操作数。
编译器会计算三种别名信息:基于类型的,流敏感的,流不敏感的。对于流敏感的别名
信息,同一个指针p的不同版本p1,p2可能指向不同的变量集合,有不同的别名集合,
所以,p1和p2分别有自己的MT,这些MT是关联在SSA_NAME上的,又称name memory tag
(NMT)。另一方面,如果编译器不能计算出流敏感的指向信息,它使用基于类型的或指
向分析得到流不敏感的别名信息。这种情况下,编译器为一个指针的所有版本创建唯一
的MT(对应于那个指针实际的声明),这称为type memory tag(TMT)。如果某个别名集合
过大,编译器使用某些启发式规则将该集合中的成员分组,每一组使用一个新建立的MT
表示。
** 下面,我们开始逐个文件的仔细分析。先来看看tree-pass.h文件。
tree-pass.h文件中定义的东西用于描述一个tree-ssa优化遍。同时,已定义的每一个
优化遍都要在这个文件中添加一个对应的外部声明。
开始的这些都是在定义gcc的dump的位置的信息的。和tree-dump.c文件中的函数配合
使用,可以将命令行指定的中间表示倒出到文件中。
%{
/* 定义不同的倒出点,如果修改这个,同时需要修改tree-dump.c中的DUMP_FILES */
enum tree_dump_index
{
TDI_none, /* 不倒出 */
TDI_cgraph, /* 倒出调用图 */
TDI_tu, /* 倒出整个翻译单元 */
TDI_class, /* 倒出类的层次图 */
TDI_original, /* 倒出优化前的每个函数 */
TDI_generic, /* 在genericizing之后,倒出每个函数 */
TDI_nested, /* 在unnesting 之后倒出每个函数 */
TDI_vcg, /* 为每个函数的流图创建一个VCG图文件 */
TDI_tree_all, /* 启用所有的GENERIC/GIMPLE倒出 */
TDI_rtl_all, /* 启用所有的RTL倒出 */
TDI_ipa_all, /* 启用所有的IPA倒出 */
TDI_end
};
/* 用于控制倒出的位掩码,并非所有的掩码都可以用于所有的倒出。可以在后面增加新的
* 掩码,此时要修改tree-dump.c中的DUMP_OPTIONS */
#define TDF_ADDRESS (1 << 0) /* 倒出节点地址 */
#define TDF_SLIM (1 << 1) /* 这是什么?don't go wild following links */
#define TDF_RAW (1 << 2) /* 这是什么?don't unparse the function */
#define TDF_DETAILS (1 << 3) /* 对每一个遍给出更详细的信息 */
#define TDF_STATS (1 << 4) /* 倒出个遍的统计信息 */
#define TDF_BLOCKS (1 << 5) /* 显示基本快的边界 */
#define TDF_VOPS (1 << 6) /* 显示虚操作数 */
#define TDF_LINENO (1 << 7) /* 显示语句的行号 */
#define TDF_UID (1 << 8) /* display decl UIDs */
#define TDF_TREE (1 << 9) /* 树的dump */
#define TDF_RTL (1 << 10) /* rtl的dump */
#define TDF_IPA (1 << 11) /* ipa的dump */
#define TDF_STMTADDR (1 << 12) /* 语句的地址 */
#define TDF_GRAPH (1 << 13) /* 图形dump */
#define TDF_MEMSYMS (1 << 14) /* 现实一个表达式中的memory符号,隐含TDF_VOPS */
#define TDF_DIAGNOSTIC (1 << 15) /* 将dump放到诊断信息中 */
/* 这些函数在tree-dump.c中实现 */
extern char *get_dump_file_name (enum tree_dump_index);
extern int dump_enabled_p (enum tree_dump_index);
extern int dump_initialized_p (enum tree_dump_index);
extern FILE *dump_begin (enum tree_dump_index, int *);
extern void dump_end (enum tree_dump_index, FILE *);
extern void dump_node (const_tree, int, FILE *);
extern int dump_switch_p (const char *);
extern const char *dump_flag_name (enum tree_dump_index);
/* 这些全局变量用于在tree-dump.c和优化遍之间交互 */
extern FILE *dump_file;
extern int dump_flags;
extern const char *dump_file_name;
/* 返回指定阶段的dump_file_info */
extern struct dump_file_info *get_dump_file_info (enum tree_dump_index);
/* 定义树dump的一些开关 */
struct dump_file_info
{
const char *suffix; /* 给定输出文件的后缀 */
const char *swtch; /* 命令行开关 */
const char *glob; /* 短命令行开关 */
int flags; /* 干什么用的?user flags */
int state; /* 干什么用的?state of play */
int num; /* dump文件号 */
int letter; /* 干什么用的?enabling letter for RTL dumps */
};
}%
上面这部分也是和优化遍有关的东西,尤其在调试gcc本身的时候会比较有用。下面这个结
构体就是真正用于描述一个遍的数据结构了。每个“遍”都要有一个这种类型的全局变量对
应。
%{
/* 用于描述一个遍 */
struct tree_opt_pass
{
/* 该遍的简要名称,用于dump文件名的一部分 */
const char *name;
/* 如果非空,则该遍以及该遍的子遍只有在这个函数返回真的时候才执行 */
bool (*gate) (void);
/* 这是该遍要执行的代码。如果为空,并且又没有任何子遍,那么这遍什么事情也不做
返回值中包含的TODOs是除了TODO_flags_finish中TODOs,的额外需要执行的TODOs*/
unsigned int (*execute) (void);
/* 子遍,如果gate返回真,它们也要运行 */
struct tree_opt_pass *sub;
/* 在passes列表中的下一个要运行的pass,与gate的返回值无关 */
struct tree_opt_pass *next;
/* 静态的pass的编号,用于dump文件名的一部分 */
int static_pass_number;
/* 与这个遍相关的计时器id,理想情况是自动地动态分配 */
unsigned int tv_id;
/* 该pass的输入和输出的属性的集合 */
unsigned int properties_required;
unsigned int properties_provided;
unsigned int properties_destroyed;
/* 该遍之前和之后需要做的事情 */
unsigned int todo_flags_start;
unsigned int todo_flags_finish;
/* 这是什么?Letter for RTL dumps. */
char letter;
};
}%
下面这些宏定义了上面代码中所说的属性。
%{
/* Pass 属性 */
#define PROP_gimple_any (1 << 0) /* 所有的gimple语法 */
#define PROP_gimple_lcf (1 << 1) /* lowered控制流 */
#define PROP_gimple_leh (1 << 2) /* lowered例外处理句柄 */
#define PROP_cfg (1 << 3) /* 控制流图*/
#define PROP_referenced_vars (1 << 4) /* 这个是什么?*/
#define PROP_ssa (1 << 5) /* ssa 形式 */
#define PROP_no_crit_edges (1 << 6) /* 没有critical边 */
#define PROP_rtl (1 << 7) /* rtl 形式*/
#define PROP_alias (1 << 8) /* 别名分析*/
#define PROP_gimple_lomp (1 << 9) /* lowered OpenMP指示字 */
#define PROP_trees \ /* 树*/
(PROP_gimple_any | PROP_gimple_lcf | PROP_gimple_leh | PROP_gimple_lomp)
}%
类似的,下面还有使用宏定义的各种TODOs。
在这个文件中还声明了一些函数:
extern void tree_lowering_passes (tree decl);实现在tree-optimize.c中,用于
lowering一个指定的树。
%{
/* 这4个函数都在passes.c中实现 */
/* 执行指定的pass列表中的所有pass及其子pass */
extern void execute_pass_list (struct tree_opt_pass *);
/* 和execute_pass_list类似,但是假设pass列表中pass的子pass都是局部的pass */
extern void execute_ipa_pass_list (struct tree_opt_pass *);
/* 可以在任意地方调用,打印出当前pass的名字和编号 */
extern void print_current_pass (FILE *);
/* 由debuger调用,打印当前pass的名字和编号,其实就是直接调用了上面的函数*/
extern void debug_pass (void);
}%
在该文件中声明了三个重要的全局变量,这三个变量中保存了所有的遍。这些遍组织成树状
,这三个变量指向树的根。
extern struct tree_opt_pass *all_passes, *all_ipa_passes, *all_lowering_passes;
好的,关于遍的描述我们就先分析到这里。tree-passes.h文件基本上也分析完了。
** 遍的驱动
其实,各个遍的执行的最直接的驱动都定义在文件passes.c中。下面我们就来看看passes.c这个文件。我们可能不按照文件中函数的顺序分析
%{
/* 用于调试,在任何时候都指向当前进行的遍 */
struct tree_opt_pass *current_pass;
/* 打印当前遍的信息 */
void
print_current_pass (FILE *file)
{
if (current_pass)
fprintf (file, "current pass = %s (%d)\n",
current_pass->name, current_pass->static_pass_number);
else
fprintf (file, "no current pass.\n");
}
/* 打印当前遍的信息到stderr上 */
void
debug_pass (void)
{
print_current_pass (stderr);
}
/* 下面这个函数会在多个地方为FUNCTION_DECL,VAR_DECL和TYPE_DECL节点调用。对于局部非静态变量,该函数什么也不做,除非这个变量是寄存器变量。在变量不是自动变量的时候,该函数会设置对应的RTL,输出汇编代码(标号定义,存储分配和初始值)。*/
void
rest_of_decl_compilation (tree decl,
int top_level,
int at_end)
/* 该函数在此不进行分析,因为里面有一部分代码现在看不懂,而且和优化遍没有太大关系*/
/* 在发现一个结构、联合或枚举类型时,调用该函数。同样不在此进行分析 */
void
rest_of_type_compilation (tree type, int toplev)
/* 该函数会在do_compile中的finalize函数中调用,完成一些收尾工作。看样子主要是输出一些dump信息。*/
void
finish_optimization_passes (void)
{
enum tree_dump_index i;
struct dump_file_info *dfi;
char *name;
timevar_push (TV_DUMP);
/* 这里处理一些剖视信息的dump */
if (profile_arc_flag || flag_test_coverage || flag_branch_probabilities)
{
dump_file = dump_begin (pass_profile.static_pass_number, NULL);
end_branch_prob ();
if (dump_file)
dump_end (pass_profile.static_pass_number, dump_file);
}
if (optimize > 0)
{
dump_file = dump_begin (pass_combine.static_pass_number, NULL);
if (dump_file)
{
dump_combine_total_stats (dump_file);
dump_end (pass_combine.static_pass_number, dump_file);
}
}
/* dump出图 */
if (graph_dump_format != no_graph)
for (i = TDI_end; (dfi = get_dump_file_info (i)) != NULL; ++i)
if (dump_initialized_p (i)
&& (dfi->flags & TDF_GRAPH) != 0
&& (name = get_dump_file_name (i)) != NULL)
{
finish_graph_dump_file (name);
free (name);
}
timevar_pop (TV_DUMP);
}
/* 所有遍组成的树的根 */
struct tree_opt_pass *all_passes, *all_ipa_passes, *all_lowering_passes;
/* 深度优先遍历遍组成的树,为每一个遍分配并注册dump文件名,和文件号,以及dump命令行开关等信息,注册的东西在tree-dump.c中 */
static void
register_one_dump_file (struct tree_opt_pass *pass, bool ipa, int properties)
{
char *dot_name, *flag_name, *glob_name;
const char *prefix;
char num[10];
int flags;
/* 参考下面的 next_pass_1.如果某个遍只存在一个版本,那么static_pass_number的
* 值是-1,在名字中没有编号,否则,在名字中按照这个遍加入的顺序从1开始编号*/
num[0] = '\0';
if (pass->static_pass_number != -1)
sprintf (num, "%d", ((int) pass->static_pass_number < 0
? 1 : pass->static_pass_number));
dot_name = concat (".", pass->name, num, NULL);
if (ipa)
prefix = "ipa-", flags = TDF_IPA;
else if (properties & PROP_trees)
prefix = "tree-", flags = TDF_TREE;
else
prefix = "rtl-", flags = TDF_RTL;
/* 这两个是命令行选项的名字 */
flag_name = concat (prefix, pass->name, num, NULL);
glob_name = concat (prefix, pass->name, NULL);
/* 完成注册,并给该遍一个真正的static_pass_number,而不是原来的副本号*/
pass->static_pass_number = dump_register (dot_name, flag_name, glob_name,
flags, pass->letter);
}
/* 为开始于pass的遍流水线注册dump文件。如果这些遍是过程间的,ipa为真。properties表示在该流水线开始时一定可用的属性。 */
static void
register_dump_files (struct tree_opt_pass *pass, bool ipa, int properties)
{
pass->properties_required |= properties;
register_dump_files_1 (pass, ipa, properties);
}
/* 这个函数递归地完成注册工作,深度优先,先序遍历,返回值表示所有以pass开始的遍完成后,一定可用的属性 */
static int
register_dump_files_1 (struct tree_opt_pass *pass, bool ipa, int properties)
{
do
{
/* 完成本pass后属性的变化 */
int new_properties = (properties | pass->properties_provided)
& ~pass->properties_destroyed;
/* 注册本pass */
if (pass->name)
register_one_dump_file (pass, ipa, new_properties);
/* 注册所有的子pass*/
if (pass->sub)
new_properties = register_dump_files_1 (pass->sub, false,
new_properties);
/* 如果这个pass有gate,那么必须保证properties里面是不管是否执行该pass,都一定可用的属性 */
if (pass->gate)
properties &= new_properties;
else
properties = new_properties;
pass = pass->next;
}
while (pass);
return properties;
}
/* 在list中增加一个新的遍,如果该遍已经在list中存在,就增加它的一个副本 */
static struct tree_opt_pass **
next_pass_1 (struct tree_opt_pass **list, struct tree_opt_pass *pass)
{
/* static_pass_number非0表示该遍已经在某个list中了 */
if (pass->static_pass_number)
{
struct tree_opt_pass *new;
/* 复制一个副本 */
new = xmalloc (sizeof (*new));
memcpy (new, pass, sizeof (*new));
new->next = NULL;
new->todo_flags_start &= ~TODO_mark_first_instance;
/* 第一个副本的static_pass_number是总副本数的负数,其它副本的static_pass_number是从2开始的顺序编号 */
if (pass->name)
{
pass->static_pass_number -= 1;
new->static_pass_number = -pass->static_pass_number;
}
*list = new;
}
else
{
pass->todo_flags_start |= TODO_mark_first_instance;
pass->static_pass_number = -1;
*list = pass;
}
return &(*list)->next;
}
/* 构造pass构成的树,所有遍的执行时被cgraph中的函数驱动的:
cgraph_finalize_compilation_unit ()
for each node N in the cgraph
cgraph_analyze_function (N)
cgraph_lower_function (N) -> all_lowering_passes
如果我们做优化,那么函数cgraph_optimize会被调用:
cgraph_optimize ()
ipa_passes () -> all_ipa_passes
cgraph_expand_all_functions ()
for each node N in the cgraph
cgraph_expand_function (N)
tree_rest_of_compilation (DECL (N)) -> all_passes
*/
/* 这个函数构造pass树 */
void
init_optimization_passes (void)
{
struct tree_opt_pass **p;
#define NEXT_PASS(PASS) (p = next_pass_1 (p, &PASS))
/* 这些lowering遍将函数变形成优化器可以操作的形式。 这些pass总是会在func上运行
但是,后端可能会产生一些已经是lowering过的形式的函数,这些函数可能并不是由这些遍处理的 */
p = &all_lowering_passes;
NEXT_PASS (pass_remove_useless_stmts);
NEXT_PASS (pass_mudflap_1);
NEXT_PASS (pass_lower_omp);
NEXT_PASS (pass_lower_cf);
NEXT_PASS (pass_refactor_eh);
NEXT_PASS (pass_lower_eh);
NEXT_PASS (pass_build_cfg);
NEXT_PASS (pass_lower_complex_O0);
NEXT_PASS (pass_lower_vector);
NEXT_PASS (pass_warn_function_return);
NEXT_PASS (pass_build_cgraph_edges);
NEXT_PASS (pass_inline_parameters);
*p = NULL;
/* 过程间优化遍
-fno-unit-at-a-time 模式下,除了early_local_passes的子遍外,这些遍会被忽略
*/
p = &all_ipa_passes;
....../* 省略一些遍的加入。。*/
/* 这些遍在IPA遍之后,在每一个需要生成汇编代码的函数上运行 */
p = &all_passes;
....../* 省略一些遍的加入。。*/
#undef NEXT_PASS
/* 注册dump文件 */
register_dump_files (all_lowering_passes, false, PROP_gimple_any);
all_lowering_passes->todo_flags_start |= TODO_set_props;
register_dump_files (all_ipa_passes, true,
PROP_gimple_any | PROP_gimple_lcf | PROP_gimple_leh
| PROP_cfg);
register_dump_files (all_passes, false,
PROP_gimple_any | PROP_gimple_lcf | PROP_gimple_leh
| PROP_cfg);
}
/* 如果在IPA模式下(current_function_decl为NULL),
* 为cgraph中的每一个函数调用指定的回调函数. 否则
为current function调用回调函数 */
static void
do_per_function (void (*callback) (void *data), void *data)
{
if (current_function_decl)
callback (data);
else
{
struct cgraph_node *node;
for (node = cgraph_nodes; node; node = node->next)
if (node->analyzed)
{
push_cfun (DECL_STRUCT_FUNCTION (node->decl));
current_function_decl = node->decl;
callback (data);
free_dominance_info (CDI_DOMINATORS);
free_dominance_info (CDI_POST_DOMINATORS);
current_function_decl = NULL;
pop_cfun ();
ggc_collect ();
}
}
}
static int nnodes;
static GTY ((length ("nnodes"))) struct cgraph_node **order;
/* 与上面的函数类似,但是按照拓扑序来调用 */
static void
do_per_function_toporder (void (*callback) (void *data), void *data)
{
int i;
if (current_function_decl)
callback (data);
else
{
gcc_assert (!order);
order = ggc_alloc (sizeof (*order) * cgraph_n_nodes);
/* 这个函数给出调用图的反拓扑序*/
nnodes = cgraph_postorder (order);
for (i = nnodes - 1; i >= 0; i--)
{
struct cgraph_node *node = order[i];
/* Allow possibly removed nodes to be garbage collected. */
order[i] = NULL;
if (node->analyzed && (node->needed || node->reachable))
{
push_cfun (DECL_STRUCT_FUNCTION (node->decl));
current_function_decl = node->decl;
callback (data);
free_dominance_info (CDI_DOMINATORS);
free_dominance_info (CDI_POST_DOMINATORS);
current_function_decl = NULL;
pop_cfun ();
ggc_collect ();
}
}
}
ggc_free (order);
order = NULL;
nnodes = 0;
}
/* 为每一个function完成所有的TODO动作,data就是TODO的flags */
static void
execute_function_todo (void *data)
{
unsigned int flags = (size_t)data;
/* 只要当前在ssa形式中,就需要verify它 */
if (cfun->curr_properties & PROP_ssa)
flags |= TODO_verify_ssa;
/* 所有刚刚验证过的东西,不必重复进行 */
flags &= ~cfun->last_verified;
if (!flags)
return;
/* 在更新ssa之前总是清理cfg */
if (flags & TODO_cleanup_cfg)
{
bool cleanup = cleanup_tree_cfg ();
if (cleanup && (cfun->curr_properties & PROP_ssa))
flags |= TODO_remove_unused_locals;
/* 清理工作可能会连接连续的基本块,它可能进行一些简单的传播,删除一些只有一个值得phi节点,所以,可能导致ssa失效,在这里一定要更新ssa */
if (!(flags & TODO_update_ssa_any) && need_ssa_update_p ())
flags |= TODO_update_ssa;
}
/* 更新ssa,该last_verified的标识 */
if (flags & TODO_update_ssa_any)
{
unsigned update_flags = flags & TODO_update_ssa_any;
update_ssa (update_flags);
cfun->last_verified &= ~TODO_verify_ssa;
}
/* 更新别名分析*/
if (flags & TODO_rebuild_alias)
{
compute_may_aliases ();
cfun->curr_properties |= PROP_alias;
}
/* 删除无用的局部变量 */
if (flags & TODO_remove_unused_locals)
remove_unused_locals ();
/* 写dump文件 */
if ((flags & TODO_dump_func)
&& dump_file && current_function_decl)
{
if (cfun->curr_properties & PROP_trees)
dump_function_to_file (current_function_decl,
dump_file, dump_flags);
else
{
if (dump_flags & TDF_SLIM)
print_rtl_slim_with_bb (dump_file, get_insns (), dump_flags);
else if ((cfun->curr_properties & PROP_cfg)
&& (dump_flags & TDF_BLOCKS))
print_rtl_with_bb (dump_file, get_insns ());
else
print_rtl (dump_file, get_insns ());
if (cfun->curr_properties & PROP_cfg
&& graph_dump_format != no_graph
&& (dump_flags & TDF_GRAPH))
print_rtl_graph_with_bb (dump_file_name, get_insns ());
}
/* Flush the file. If verification fails, we won't be able to
close the file before aborting. */
fflush (dump_file);
}
/* 更新基本块的执行频率*/
if (flags & TODO_rebuild_frequencies)
{
if (profile_status == PROFILE_GUESSED)
{
loop_optimizer_init (0);
add_noreturn_fake_exit_edges ();
mark_irreducible_loops ();
connect_infinite_loops_to_exit ();
estimate_bb_frequencies ();
remove_fake_exit_edges ();
loop_optimizer_finalize ();
}
else if (profile_status == PROFILE_READ)
counts_to_freqs ();
else
gcc_unreachable ();
}
/*验证正确的工作,用于调试*/
#if defined ENABLE_CHECKING
if (flags & TODO_verify_ssa)
verify_ssa (true);
if (flags & TODO_verify_flow)
verify_flow_info ();
if (flags & TODO_verify_stmts)
verify_stmts ();
if (flags & TODO_verify_loops)
verify_loop_closed_ssa ();
if (flags & TODO_verify_rtl_sharing)
verify_rtl_sharing ();
#endif
cfun->last_verified = flags & TODO_verify_all;
}
/* 完成所有的TODO动作 */
static void
execute_todo (unsigned int flags)
{
/* 用于调试的 */
#if defined ENABLE_CHECKING
if (need_ssa_update_p ())
gcc_assert (flags & TODO_update_ssa_any);
#endif
/* 全局变量指出,该遍是不是第一次运行 */
first_pass_instance = (flags & TODO_mark_first_instance) != 0;
/* 完成实际的工作*/
do_per_function (execute_function_todo, (void *)(size_t) flags);
/* inlining之前删除无用的函数:
* IPA遍可能想看到那些没有被inline的外部函数的函数体来分析副作用
* 在IPA结束的时候会删除这些无用的函数 */
if (flags & TODO_remove_functions)
cgraph_remove_unreachable_nodes (true, dump_file);
/* dump 出cgraph */
if ((flags & TODO_dump_cgraph)
&& dump_file && !current_function_decl)
{
dump_cgraph (dump_file);
/* Flush the file. If verification fails, we won't be able to
close the file before aborting. */
fflush (dump_file);
}
if (flags & TODO_ggc_collect)
{
ggc_collect ();
}
/* 这个是做什么的?大概好像是某些错误是可以忽略的,就在这里忽略 */
if (flags & TODO_df_finish)
df_finish_pass ((flags & TODO_df_verify) != 0);
}
/* 验证遍之间应该始终保持的不变量,这里可以放置简单的验证条件 */
static void
verify_interpass_invariants (void)
{
#ifdef ENABLE_CHECKING
gcc_assert (!fold_deferring_overflow_warnings_p ());
#endif
}
/* 清楚上次验证的标志 */
static void
clear_last_verified (void *data ATTRIBUTE_UNUSED)
{
cfun->last_verified = 0;
}
/* 执行完一遍之后,更新当前函数的属性 */
static void
update_properties_after_pass (void *data)
{
struct tree_opt_pass *pass = data;
cfun->curr_properties = (cfun->curr_properties | pass->properties_provided)
& ~pass->properties_destroyed;
}
/* 执行一个指定的遍 */
static bool
execute_one_pass (struct tree_opt_pass *pass)
{
bool initializing_dump;
unsigned int todo_after = 0;
current_pass = pass;
/* 如果该遍被gate函数约束,并且gate函数返回假,那么不执行这遍 */
if (pass->gate && !pass->gate ())
return false;
/* 如果quiet_flag为真,并且cfun不为空,开始在标准输出上打印信息*/
if (!quiet_flag && !cfun)
fprintf (stderr, " <%s>", pass->name ? pass->name : "");
/* 该遍执行之前需要执行的动作 */
if (pass->todo_flags_start & TODO_set_props)
cfun->curr_properties = pass->properties_required;
/* 用全局变量指示出是否在gimple形式 */
in_gimple_form = (cfun && (cfun->curr_properties & PROP_trees)) != 0;
/* 执行遍之前需要的操作 */
execute_todo (pass->todo_flags_start);
#ifdef ENABLE_CHECKING
do_per_function (verify_curr_properties,
(void *)(size_t)pass->properties_required);
#endif
/* 如果dump文件名存在,在启用的情况下打开它 */
if (pass->static_pass_number != -1)
{
initializing_dump = !dump_initialized_p (pass->static_pass_number);
dump_file_name = get_dump_file_name (pass->static_pass_number);
dump_file = dump_begin (pass->static_pass_number, &dump_flags);
if (dump_file && current_function_decl)
{
const char *dname, *aname;
dname = lang_hooks.decl_printable_name (current_function_decl, 2);
aname = (IDENTIFIER_POINTER
(DECL_ASSEMBLER_NAME (current_function_decl)));
fprintf (dump_file, "\n;; Function %s (%s)%s\n\n", dname, aname,
cfun->function_frequency == FUNCTION_FREQUENCY_HOT
? " (hot)"
: cfun->function_frequency == FUNCTION_FREQUENCY_UNLIKELY_EXECUTED
? " (unlikely executed)"
: "");
}
}
else
initializing_dump = false;
/* 如果有计时器,启动它 */
if (pass->tv_id)
timevar_push (pass->tv_id);
/* 完成该遍指定的操作 */
if (pass->execute)
{
todo_after = pass->execute ();
do_per_function (clear_last_verified, NULL);
}
/* 停止计时器 */
if (pass->tv_id)
timevar_pop (pass->tv_id);
/* 更新函数的属性*/
do_per_function (update_properties_after_pass, pass);
if (initializing_dump
&& dump_file
&& graph_dump_format != no_graph
&& (cfun->curr_properties & (PROP_cfg | PROP_rtl))
== (PROP_cfg | PROP_rtl))
{
get_dump_file_info (pass->static_pass_number)->flags |= TDF_GRAPH;
dump_flags |= TDF_GRAPH;
clean_graph_dump_file (dump_file_name);
}
/* 运行遍之后需要完成的动作 */
execute_todo (todo_after | pass->todo_flags_finish);
verify_interpass_invariants ();
/* 新生成了一些函数,需要cgraph处理并加入它们 */
if (!current_function_decl)
cgraph_process_new_functions ();
/* 关闭dump文件 */
if (dump_file_name)
{
free (CONST_CAST (char *, dump_file_name));
dump_file_name = NULL;
}
if (dump_file)
{
dump_end (pass->static_pass_number, dump_file);
dump_file = NULL;
}
current_pass = NULL;
/* 重置该全局变量,否则会在non-unit-at-a-time下出错. */
in_gimple_form = false;
return true;
}
/* 递归执行一个pass列表 */
void
execute_pass_list (struct tree_opt_pass *pass)
{
do
{
/* 执行当前遍,递归执行其子遍 */
if (execute_one_pass (pass) && pass->sub)
execute_pass_list (pass->sub);
pass = pass->next;
}
while (pass);
}
/* 和上面类似,但是被执行的IPA的遍的子遍都是local遍,并且按照拓扑序执行 */
void
execute_ipa_pass_list (struct tree_opt_pass *pass)
{
do
{
gcc_assert (!current_function_decl);
gcc_assert (!cfun);
if (execute_one_pass (pass) && pass->sub)
do_per_function_toporder ((void (*)(void *))execute_pass_list,
pass->sub);
if (!current_function_decl)
cgraph_process_new_functions ();
pass = pass->next;
}
while (pass);
}
}%
**
在文件tree-optimize.c中定义了几个遍,这些遍基本上不完成什么具体的功能,而是用来控制其它遍的执行的。我们下面来看一下。
%{
/* 这个遍在all_passes中,inline的后面。大量局部的优化遍都注册为它的子遍*/
struct tree_opt_pass pass_all_optimizations =
{
NULL, /* name */
gate_all_optimizations, /* gate */
NULL, /* execute */
NULL, /* sub */
NULL, /* next */
0, /* static_pass_number */
0, /* tv_id */
0, /* properties_required */
0, /* properties_provided */
0, /* properties_destroyed */
0, /* todo_flags_start */
0, /* todo_flags_finish */
0 /* letter */
};
/* 上面的遍的gate,可见,在optimize>=1的时候就会执行,这些都是“优化遍”*/
static bool
gate_all_optimizations (void)
{
return (optimize >= 1
/* Don't bother doing anything if the program has errors.
We have to pass down the queue if we already went into SSA */
&& (!(errorcount || sorrycount) || gimple_in_ssa_p (cfun)));
}
/* 这个注册在all_ipa_passes中*/
struct tree_opt_pass pass_early_local_passes =
{
"early_local_cleanups", /* name */
gate_all_early_local_passes, /* gate */
NULL, /* execute */
NULL, /* sub */
NULL, /* next */
0, /* static_pass_number */
0, /* tv_id */
0, /* properties_required */
0, /* properties_provided */
0, /* properties_destroyed */
0, /* todo_flags_start */
TODO_remove_functions, /* todo_flags_finish */
0 /* letter */
};
static bool
gate_all_early_local_passes (void)
{
/* 只要没有错,就会执行 */
return (!errorcount && !sorrycount);
}
/* 注册在pass_early_local_passes的子遍中,用于控制一些其他前期优化遍*/
struct tree_opt_pass pass_all_early_optimizations =
{
"early_optimizations", /* name */
gate_all_early_optimizations, /* gate */
execute_early_local_optimizations, /* execute */
NULL, /* sub */
NULL, /* next */
0, /* static_pass_number */
0, /* tv_id */
0, /* properties_required */
0, /* properties_provided */
0, /* properties_destroyed */
0, /* todo_flags_start */
0, /* todo_flags_finish */
0 /* letter */
};
static bool
gate_all_early_optimizations (void)
{
/* 在优化级别大于等于1时才执行*/
return (optimize >= 1
/* Don't bother doing anything if the program has errors. */
&& !(errorcount || sorrycount));
}
/* 该遍自己唯一的行为就是在flag_unit_at_a_time的情况下设置cgraph_state*/
static unsigned int
execute_early_local_optimizations (void)
{
if (flag_unit_at_a_time)
cgraph_state = CGRAPH_STATE_IPA_SSA;
return 0;
}
/* 清理CFG的遍, 在pass_early_local_optimization和
* pass_early_local_pass中注册。它没有子遍了*/
struct tree_opt_pass pass_cleanup_cfg =
{
"cleanup_cfg", /* name */
NULL, /* gate */
execute_cleanup_cfg_pre_ipa, /* execute */
NULL, /* sub */
NULL, /* next */
0, /* static_pass_number */
0, /* tv_id */
PROP_cfg, /* properties_required */
0, /* properties_provided */
0, /* properties_destroyed */
0, /* todo_flags_start */
TODO_dump_func, /* todo_flags_finish */
0 /* letter */
};
static unsigned int
execute_cleanup_cfg_pre_ipa (void)
{
cleanup_tree_cfg ();
return 0;
}
/* 在将树变成RTL之前的清理CFG的遍。这个仅仅进行标号的清理和case节点的分组工作
* 在优化之后,这一遍是可能需要的,注册在all_passes中几乎最后的位置*/
struct tree_opt_pass pass_cleanup_cfg_post_optimizing =
{
"final_cleanup", /* name */
NULL, /* gate */
execute_cleanup_cfg_post_optimizing, /* execute */
NULL, /* sub */
NULL, /* next */
0, /* static_pass_number */
0, /* tv_id */
PROP_cfg, /* properties_required */
0, /* properties_provided */
0, /* properties_destroyed */
0, /* todo_flags_start */
TODO_dump_func, /* todo_flags_finish */
0 /* letter */
};
/*执行的动作*/
static unsigned int
execute_cleanup_cfg_post_optimizing (void)
{
fold_cond_expr_cond ();
cleanup_tree_cfg ();
cleanup_dead_labels ();
group_case_labels ();
return 0;
}
/* 下面的这两个遍做一些清理工作,主要是释放不用的数据结构*/
struct tree_opt_pass pass_free_datastructures
struct tree_opt_pass pass_free_cfg_annotations
/* 这个遍用于初始化优化使用的数据结构,在优化级别大于等于1时执行*/
struct tree_opt_pass pass_init_datastructures
/* 在这个文件中还定义了三个不属于任何遍的函数,用于完成一些常用功能 */
/* 为函数fn执行所有的lowering 遍,还有可能执行pass_early_local_passes*/
void
tree_lowering_passes (tree fn)
{
tree saved_current_function_decl = current_function_decl;
current_function_decl = fn;
push_cfun (DECL_STRUCT_FUNCTION (fn));
tree_register_cfg_hooks ();
bitmap_obstack_initialize (NULL);
execute_pass_list (all_lowering_passes);
if (optimize && cgraph_global_info_ready)
execute_pass_list (pass_early_local_passes.sub);
free_dominance_info (CDI_POST_DOMINATORS);
free_dominance_info (CDI_DOMINATORS);
compact_blocks ();
current_function_decl = saved_current_function_decl;
bitmap_obstack_release (NULL);
pop_cfun ();
}
/* 这个函数会为指定的函数fndecl执行所有的优化遍,并且基本上完成了这个函数的编译
* 工作*/
void
tree_rest_of_compilation (tree fndecl)
{
location_t saved_loc;
struct cgraph_node *node;
timevar_push (TV_EXPAND);
gcc_assert (!flag_unit_at_a_time || cgraph_global_info_ready);
node = cgraph_node (fndecl);
/* Initialize the default bitmap obstack. */
bitmap_obstack_initialize (NULL);
/* 初始化该函数的RTL代码 */
current_function_decl = fndecl;
saved_loc = input_location;
input_location = DECL_SOURCE_LOCATION (fndecl);
init_function_start (fndecl);
/* 即便我们在一个函数体内,仍然不想调用expand_expr来计算一个变长数组的大小*/
cfun->x_dont_save_pending_sizes_p = 1;
tree_register_cfg_hooks ();
bitmap_obstack_initialize (®_obstack); /* FIXME, only at RTL generation*/
/* 执行所有树的变换和优化 */
execute_pass_list (all_passes);
bitmap_obstack_release (®_obstack);
/* Release the default bitmap obstack. */
bitmap_obstack_release (NULL);
DECL_SAVED_TREE (fndecl) = NULL;
set_cfun (NULL);
/* 在启用的情况下,如果一个函数返回了一个大的数据结构,给出警告 */
if (warn_larger_than && !DECL_EXTERNAL (fndecl) && TREE_TYPE (fndecl))
{
tree ret_type = TREE_TYPE (TREE_TYPE (fndecl));
if (ret_type && TYPE_SIZE_UNIT (ret_type)
&& TREE_CODE (TYPE_SIZE_UNIT (ret_type)) == INTEGER_CST
&& 0 < compare_tree_int (TYPE_SIZE_UNIT (ret_type),
larger_than_size))
{
unsigned int size_as_int
= TREE_INT_CST_LOW (TYPE_SIZE_UNIT (ret_type));
if (compare_tree_int (TYPE_SIZE_UNIT (ret_type), size_as_int) == 0)
warning (OPT_Wlarger_than_, "size of return value of %q+D is %u bytes",
fndecl, size_as_int);
else
warning (OPT_Wlarger_than_, "size of return value of %q+D is larger than %wd bytes",
fndecl, larger_than_size);
}
}
if (!flag_inline_trees)
{
DECL_SAVED_TREE (fndecl) = NULL;
if (DECL_STRUCT_FUNCTION (fndecl) == 0
&& !cgraph_node (fndecl)->origin)
{
/* 不再执行将被释放的局部节点
但是DECL_INITIAL必须非空,这样才表示一个真正的函数。
对于嵌套函数,在c_pop_function_context完成.
如果rest_of_compilation设置它为0,不要改变 */
if (DECL_INITIAL (fndecl) != 0)
DECL_INITIAL (fndecl) = error_mark_node;
}
}
input_location = saved_loc;
ggc_collect ();
timevar_pop (TV_EXPAND);
}
}%
阅读(5287) | 评论(0) | 转发(0) |