5.13.5. Code analysis and optimization
5.13.5.1. Preliminaries- alias set analysis
5.13.5.1.1. Concept of alias set
In wikipedia , there is a good explaination of alias set given below.
Alias analysis is a technique in compiler theory, used to determine if a storage location may be accessed in more than one way. Two pointers are said to be aliased if they point to the same location. Alias analysis techniques are usually classified by flow-sensitivity and context-sensitivity. They may determine may-alias or must-alias information. The term alias analysis is often used interchangeably with term points-to analysis, a specific case. What Does Alias Analysis Do? In general, alias analysis determines whether or not separate memory references point to the same area of memory. This allows the compiler to determine what variables in the program will be affected by a statement. For example, consider the following section of code that accesses members of structures: ...; p.foo = 1; q.foo = 2; i = p.foo + 3; ... There are three pos sible alias cases here: The variables p and q cannot alias. The variables p and q must alias. It cannot be conclusively determined at compile time if p and q alias or not. If p and q cannot alias, then i = p.foo + 3; can be changed to i = 4. If p and q must alias, then i = p.foo + 3; can be changed to i = 5. In both cases, we are able to perform optimizations from the alias knowledge. On the other hand, if it is not known if p and q alias or not, then no optimizations can be performed and the whole of the code must be executed to get the result. Two memory references are said to have a may-alias relation if their aliasing is unknown. Performing Alias Analysis In alias analysis, we divide the program's memory into alias classes . Alias classes are disjoint sets of locations that cannot alias to one another. For the discussion here, it is assumed that the optimizations done here occur on a low-level intermediate representation of the program. This is to say that the program has been compiled into binary operations, jumps, moves between registers, moves from registers to memory, moves from memory to registers, branches, and function calls/returns. Type Based Alias Analysis If the language being compiled is type safe, the compiler's type checker is correct, and the language lacks the ability to create pointers referencing local variables, (such as ML, Haskell, or Java) then some useful optimizations can be made. There are many cases where we know that two memory locations must be in different alias classes: 1. Two variables of different types cannot be in the same alias class since it is a property of strongly typed, memory reference-free (i.e. references to memory locations cannot be changed directly) languages that two variables of different types cannot share the same memory location simultaneously. 2. Allocations local to the current stack frame cannot be in the same alias class as any previous allocation from another stack frame. This is the case because new memory allocations must be disjoint from all other memory allocations. 3. Each record field of each record type has its own alias class, in general, because the typing discipline usually only allows for records of the same type to alias. Since all records of a type will be stored in an identical format in memory, a field can only alias to itself. 4. Similarly, each array of a given type has its own alias class. When performing alias analysis for code, every load and store to memory needs to be labeled with its class. We then have the useful property, given memory locations Ai and Bj with i, j alias classes, that if i = j then Ai may-alias Bj , and if Flow Based Alias Analysis Analysis based on flow, unlike type based analysis, can be applied to programs in a language with references or type-casting. Flow based analysis can be used in lieu of or to supplement type based analysis. In flow based analysis, new alias classes are created for each memory allocation, and for every global and local variable whose address has been used. References may point to more than one value over time and thus may be in more than one alias class. This means that each memory location has a set of alias classes instead of a single alias class. |
No doubt, in GNU C++, both flow based alias analysis and type based analysis are used. As we have seen, for items consuming memory (majorly declarations and constants), they has MEM node associated.
In the compiler, the alias sets assigned to MEMs assist the back-end in determining which MEMs can alias which other MEMs. In general, two MEMs having different alias sets cannot alias each other, with one important exception.
Consider something like: struct S { int i; double d; };
A store to a `S' can alias something (in fact in C++, must be pointer of refernce) of either type `int' or type `double'. (However, a store to an `int' cannot alias a `double' and vice versa.) We indicate this via a tree structure that looks like:
struct S
/ /
/ /
|/_ _/|
int double
(The arrows are directed and point downwards.) In this situation we say the alias set for `struct S' is the `superset' and that those for `int' and `double' are `subsets'.
To see whether two alias sets can point to the same memory, we must see if either alias set is a subset of the other. We need not trace past immediate descendants, however, since we propagate all grandchildren up one level.
Alias set zero is implicitly a superset of all other alias sets. However, this is no actual entry for alias set zero. It is an error to attempt to explicitly construct a subset of zero.
5.13.5.1.2. The data structure
In another words, alias set is used to describe the hierarchy and relationship of the memory block that has been accessed in the program. For this purpose, the compiler defines a structure alias_set_entry for alias_set.
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;
In which, field alias_set at line 80 if zero, means the associated MEM is not in any alias set, and may alias anything (in fact, if alias analysis is in used, alias_set should not be 0, see below). This alias_set also acts as the unique id for alias sets. At line 89, field children is a pointer of splay tree. The splay tree is kind of binary tree and uses alias_set as the order key. And in the node of this splay tree, its value field is unused. So see every alias_set_entry corresponding to each alias set would hold a splay tree if it aliases to other alias sets. And the poistion of the nodes in the tree is good enough to describe the alias relationship.
New alias_set_entry allocated for alias set is done by new_alias_set as below. Flag flag_strict_aliasing is set if optimization option is higher than –O2, for which we should do language-dependent alias analysis.
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 }
Global variable last_alias_set records the number of alias_set_entry allocated so far, and it would be set in alias_set in the alias_set_entry allocated this time.
And function record_alias_subset helps to construct the alias tree by inserting subset into the tree rooted by superset to make superset being a superset of 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 }
See that creating superset of value 0 is not allowed, because alias set of 0 must be confined within some alias set to indicate its all-round accessiblity to the confining set. Without this contraint, it is possible to create a tree rooted by the alias set of 0; it definitely will crash the compiler. And note that at line 669, if subset is 0, no real entity would be created, but just remind superset it has child of value 0. And line 678 tells this flag would be propagated up into parent.
The flag has_zero_child above is majorly used in checking alias set conflict taken by below function. It returns 1 if the two specified alias sets may conflict, which means the associated objects may alias to each other.
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 }
This function should be used carefully. Tree objects behind parameter set1 and set2 must stay at the same type tree (and as result, set1 and set2 if nonzero come from the same tree of alias set). See that may-alias is a conservative predication, two objects if having same member layout (so leads to set1 equates to set2 ) are regarded as may alias.