您的位置:首页 > 编程语言

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

),它们被视为可能别名。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: