GCC源码分析(3):Tree-SSA优化框架

转自:http://blog.chinaunix.net/uid-13800995-id-67969.html

* 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);
}
}%

你可能感兴趣的:(GCC源码分析(3):Tree-SSA优化框架)