GCC-3.4.6源代码学习笔记(165)

5.13.5.      代码分析及优化

5.13.5.1.              预备知识- 别名集分析

5.13.5.1.1.        别名集概念

在维基百科( wikipedia )中,给出了如下的别名集的一个很好的解释。

别名分析 是在编译器理论中的一个技术,用于确定一个存储位置是否可能以多个方式访问。两个指针通常被认为别名,如果它们指向同一个位置。

别名分析技术通常分为流 - 敏感( flow-sensitivity )及上下文 - 敏感( context-sensitivity )。它们可以确定可能别名( may-alias )或必须别名( must-alias )的信息。术语别名分析 通常与术语指向分析 互用,一个具体的案例。

别名分析做什么用?

大体上,别名分析确定不同的内存引用是否指向内存的同一个区域。这允许编译器确定在程序中,一个语句将影响哪些变量。例如,考虑下面访问结构体成员的代码:

...; p.foo = 1; q.foo = 2; i = p.foo + 3; ...

这里有 3 个可能的别名情形:

变量 p q 不能是别名。

变量 p q 必定是别名。

在编译时刻,不能确定地决定 p q 是否是别名。

如果 p q 不能是别名,那么 i = p.foo + 3; 可以被改为 i = 4 。如果 p q 必须是别名,那么 i = p.foo + 3; 可以被改为 i = 5 。在这 2 个情形下,通过该别名的了解,我们可以执行优化。另一方面,如果不能知道 p q 是否是别名,那么就不能执行优化,而必须执行完整的代码来得到结果。两个内存引用被称为具有一个可能别名( may-alias )的关系,如果它们的别名使用是未知的。

执行别名分析

在别名分析中,我们把程序的内存分成别名组( alias classes )。别名组是不相接的位置集合,它们彼此间不是别名。为了这里的讨论,假定这里执行的优化发生在程序的一个低级的中间表达方式中。这即是说该程序已经被编译成二元操作( binary operation ),跳转( jump ),寄存器间转移( moves between registers ),寄存器到内存转移( moves from registers to memory ),内存到寄存器转移( moves from memory to registers ),分支( branche ),及函数调用、返回( function calls/returns )。

基于类型的别名分析

如果被编译的语言是类型安全的,编译器的类型检查器是正确的,并且该语言缺少创建引用局部变量的指针的能力,(例如 ML Haskell Java )那么可以进行某些有用的优化。在有很多情况下,我们知道两个内存位置必须在不同的别名组中:

1.       两个不同类型的变量不能在同一个别名组中,因为强类型,没有内存引用(即,内存位置的引用不能直接改变)语言的一个属性是,不同类型的两个变量不能同时共享同一个内存位置。

2.       相对当前栈框的局部分配,不能与之前其它栈框中的分配,在同一个别名组中。这个情形是因为新的内存分配必须与其它内存分配不相邻。

3.       每个记录( record )类型的每个记录域( record field )有其自己的别名组,大体上,因为类型规则( typing discipline )通常仅允许别名相同类型的记录。因为一个类型的所有记录在内存中将被以相同格式存储,一个域不能是它自身的别名。

4.       类似地,一个给定类型的每个数组有其自己的别名组。

当对代码执行别名分析时,对内存的每次存、取,需要用其组别标记。那么我们具有这个有用的属性,给定具有 i j 别名组的内存位置 Ai Bj ,如果 i = j ,则 Ai 可能别名( may-alias )于 Bj ,而如果 i /neq j ,则这些内存位置将不是别名。

基于流的别名分析

基于流的别名分析,不同于基于类型的别名分析,可以被应用于一个具有引用或类型转换( type-casting )的语言的程序中。具有流的分析可以被用于代替或补充基于类型的分析。在基于流的分析中,为每个内存位置,每个地址被使用的全局及局部变量,构建新的别名组。随着时间的推移,引用可能指向多于一个值,并因而属于多个别名组。这意味着每个内存位置具有一组别名组,而不是单个别名组。

毫无疑问,在 GNU C++ 中,基于流的分析及基于类型的分析都使用了。正如我们已经看到的,对于消耗内存的项(主要是声明及常量),它们具有关联的 MEM 节点。

在编译器中,赋给 MEM 的别名集帮助后端确定哪个 MEM 是其他 MEM 的别名。大体上,两个具有不同别名集的 MEM 不能互为别名,除了一个重要的例外。

考虑形如: struct S { int i; double d; };

对一个‘ S ’的存储可以是某些具有类型‘ int ’或类型‘ double ’的东西的别名(事实上,在 C++ 中,必须是指针或引用)。(不过,对一个‘ int ’的存储不能是一个‘ double ’的存储的别名,反之亦然)。我们通过一个如下的树结构来表示:

           struct S

            /   /

   /     /

        |/_     _/|

        int    double

(箭头是有方向的,并指向下)。在这个情形下,我们称对应‘ struct S ’的别名集为‘超集’,而那些对应‘ int ’及‘ double ’的别名集为‘子集’。

为了查看两个别名集是否可以指向同一块内存,我们必须检查别名集之一是否是其他别名集的子集。不过,我们不需要跟踪过去的直接后代,因为我们把所有孙辈传播到上一级。

别名集 0 隐含地是所有其他别名集的一个超集。不过,对于别名集 0 ,并没有真正的对应的项。尝试显式地构建 0 的一个子集是一个错误。

5.13.5.1.2.        数据结构

换句话说,别名集被用于描述在程序中所访问的内存块的层次及关系。出于这个目的,编译器为别名集定义了结构体 alias_set_entry

 

77    struct alias_set_entry GTY(())                                                                     in alias.c

78    {

79      /* The alias set number, as stored in MEM_ALIAS_SET.  */

80      HOST_WIDE_INT alias_set;

81   

82      /* The children of the alias set. These are not just the immediate

83        children, but, in fact, all descendants. So, if we have:

84   

85           struct T { struct S s; float f; }

86   

87        continuing our example above, the children here will be all of

88        `int', `double', `float', and `struct S'.  */

89      splay_tree GTY((param1_is (int), param2_is (int))) children;

90   

91      /* Nonzero if would have a child of zero: this effectively makes this

92        alias set the same as alias set zero.  */

93      int has_zero_child;

94    };

95    typedef struct alias_set_entry *alias_set_entry;

 

其中,在 80 行的域 alias_set ,如果是 0 ,表示关联的 MEM 不在任一别名集中,并且可能是任何对象的别名(事实上,如果使用了别名分析, alias_set 不应该是 0 ,参见下面)。这个 alias_set 也作为别名集的唯一的 id 。在 89 行,域 children 是伸展树的一个指针。这个伸展树是一种二叉树,使用 alias_set 作为排序键值。在这个伸展树的节点中,其 value 域是不使用的。因此看到每个对应别名集的 alias_set_entry 都将保存一个伸展树,如果它别名于其他别名集。并且在树中节点的位置足以描述这个别名关系。

为别名集分配新的 alias_set_entry 由下面的 new_alias_set 完成。如果优化选项高于 -O2 ,设置标记 flag_strict_aliasing ,为之我们应该执行依赖于语言的别名分析。

 

614  HOST_WIDE_INT

615  new_alias_set (void)                                                                                   in alias.c

616  {

617    if (flag_strict_aliasing )

618    {

619      if (!alias_sets )

620        VARRAY_GENERIC_PTR_INIT (alias_sets , 10, "alias sets");

621      else

622        VARRAY_GROW (alias_sets , last_alias_set + 2);

623      return ++last_alias_set ;

624    }

625    else

626      return 0;

627  }

 

全局变量 last_alias_set 记录到此为止所分配的 alias_set_entry 的数目,它将被设置在这次分配的 alias_set_entry alias_set 中。

而函数 record_alias_subset ,通过把 subset 插入由 superset 为根节点的树来构建别名树,使得 superset 成为 subset 的超集。

 

642  void

643  record_alias_subset (HOST_WIDE_INT superset, HOST_WIDE_INT subset) in alias.c

644  {

645    alias_set_entry superset_entry;

646    alias_set_entry subset_entry;

647 

648    /* It is possible in complex type situations for both sets to be the same,

649      i n which case we can ignore this operation.  */

650    if (superset == subset)

651      return ;

652 

653    if (superset == 0)

654      abort ();

655 

656    superset_entry = get_alias_set_entry (superset);

657    if (superset_entry == 0)

658    {

659      /* Create an entry for the SUPERSET, so that we have a place to

660        attach the SUBSET.  */

661      superset_entry = ggc_alloc (sizeof (struct alias_set_entry));

662      superset_entry->alias_set = superset;

663      superset_entry->children

664          = splay_tree_new_ggc (splay_tree_compare_ints);

665      superset_entry->has_zero_child = 0;

666      VARRAY_GENERIC_PTR (alias_sets , superset) = superset_entry;

667    }

668 

669    if (subset == 0)

670      superset_entry->has_zero_child = 1;

671    else

672    {

673      subset_entry = get_alias_set_entry (subset);

674      /* If there is an entry for the subset, enter all of its children

675        (if they are not already present) as children of the SUPERSET.  */

676      if (subset_entry)

677      {

678        if (subset_entry->has_zero_child)

679          superset_entry->has_zero_child = 1;

680 

681        splay_tree_foreach (subset_entry->children, insert_subset_children,

682                         superset_entry->children);

683      }

684 

685      /* Enter the SUBSET itself as a child of the SUPERSET.  */

686      splay_tree_insert (superset_entry->children,

687                     (splay_tree_key) subset, 0);

688    }

689  }

 

看到构建具有值 0 的超集是不允许的,因为别名集 0 必须被局限在某些别名集中,以表示其对其限制集的全面访问性。没有这个限制,就有可能构建一个以别名集 0 为根的树;这肯定会使编译器崩溃。注意到在 669 行,如果子集是 0 ,将不会构建真正的实体,而仅是提醒这个超集具有值 0 的后代。而 678 行表明这个标记将被传播入父辈中。

上面的标记 has_zero_child 主要用在由下面函数执行的别名集冲突检查。如果指定的两个别名集可能冲突,它返回 1 ,表明关联的对象可能彼此别名。

 

260  int

261  alias_sets_conflict_p (HOST_WIDE_INT set1, HOST_WIDE_INT set2)               in alias.c

262  {

263    alias_set_entry ase;

264 

265    /* If have no alias set information for one of the operands, we have

266      to assume it can alias anything.  */

267    if (set1 == 0 || set2 == 0

268        /* If the two alias sets are the same, they may alias.  */

269        || set1 == set2)

270      return 1;

271 

272    /* See if the first alias set is a subset of the second.  */

273    ase = get_alias_set_entry (set1);

274    if (ase != 0

275        && (ase->has_zero_child

276            || splay_tree_lookup (ase->children,

277                               (splay_tree_key) set2)))

278      return 1;

279 

280    /* Now do the same, but with the alias sets reversed.  */

281    ase = get_alias_set_entry (set2);

282    if (ase != 0

283        && (ase->has_zero_child

284             || splay_tree_lookup (ase->children,

285                               (splay_tree_key) set1)))

286      return 1;

287 

288    /* The two alias sets are distinct and neither one is the

289      child of the other. Therefore, they cannot alias.  */

290    return 0;

291  }

 

这个函数应该被仔细使用。参数 set1 set2 背后的树对象必须在同一个类型树上(而作为结果 set1 set2 ,如果非 0 ,来自别名集的同一棵树)。看到可能别名( may-alias )是一个保守的预测,如果两个对象具有相同的成员布局(因而导致 set1 等于 set2 ),它们被视为可能别名。

你可能感兴趣的:(优化,struct,tree,编译器,conflict,behavior)