GCC-3.4.6源代码学习笔记(165)
2011-02-19 11:53
218 查看
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 ,而如果 ,则这些内存位置将不是别名。 基于流的别名分析 基于流的别名分析,不同于基于类型的别名分析,可以被应用于一个具有引用或类型转换( 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
),它们被视为可能别名。
相关文章推荐
- GCC-3.4.6源代码学习笔记(55)
- GCC-3.4.6源代码学习笔记(162-续)
- GCC-3.4.6源代码学习笔记(10续3)
- GCC-3.4.6源代码学习笔记(142-续1)
- GCC-3.4.6源代码学习笔记(99)
- GCC-3.4.6源代码学习笔记(147-续1)
- GCC-3.4.6源代码学习笔记(40)
- GCC-3.4.6源代码学习笔记(122)
- GCC-3.4.6源代码学习笔记(126)
- GCC-3.4.6源代码学习笔记(135)
- GCC-3.4.6源代码学习笔记(179)
- GCC-3.4.6源代码学习笔记(25续2)
- GCC-3.4.6源代码学习笔记(8)
- GCC-3.4.6源代码学习笔记(31)
- GCC-3.4.6源代码学习笔记(37)
- GCC-3.4.6源代码学习笔记(50)
- GCC-3.4.6源代码学习笔记(154)
- GCC-3.4.6源代码学习笔记(77)
- GCC-3.4.6源代码学习笔记(22)
- GCC-3.4.6源代码学习笔记(42)