ack/doc/ego/ra/ra3

384 lines
14 KiB
Plaintext
Raw Normal View History

1987-03-03 10:59:52 +00:00
.NH 2
The register allocation phase
.PP
.NH 3
Overview
.PP
The RA phase deals with one procedure at a time.
For every procedure, it first determines which entities
may be put in a register. Such an entity
is called an \fIitem\fR.
For every item it decides during which parts of the procedure it
might be assigned a register.
Such a region is called a \fItimespan\fR.
For any item, several (possibly overlapping) timespans may
be considered.
A pair (item,timespan) is called an \fIallocation\fR.
If the items of two allocations are both live at some
point of time in the intersections of their timespans,
these allocations are said to be \fIrivals\fR of each other,
as they cannot be assigned the same register.
The rivals-set of every allocation is computed.
Next, the gains of assigning a register to an allocation are estimated,
for every allocation.
With all this information, decisions are made which allocations
to store in which registers (\fIpacking\fR).
Finally, the EM text is transformed to reflect these decisions.
.NH 3
The item recognition subphase
.PP
RA tries to put the following entities in a register:
.IP -
a local variable for which a register message was found
.IP -
the address of a local variable for which no
register message was found
.IP -
the address of a global variable
.IP -
the address of a procedure
.IP -
a numeric constant.
.LP
Only the \fIaddress\fR of a global variable
may be put in a register, not the variable itself.
This approach avoids the very complex problems that would be
caused by procedure calls and indirect pointer references (see
.[~[
aho design compiler
.] sections 14.7 and 14.8]
and
.[~[
spillman side-effects
.]]).
Still, on most machines accessing a global variable using indirect
addressing through a register is much cheaper than
accessing it via its address.
Similarly, if the address of a procedure is put in a register, the
procedure can be called via an indirect call.
.PP
With every item we associate a register type.
This type is
.DS
for local variables: the type contained in the register message
for addresses of variables and procedures: the pointer type
for constants: the general type
.DE
An entity other than a local variable is not taken to be an item
if it is used only once within the current procedure.
.PP
An item is said to be \fIlive\fR at some point of the program text
if its value may be used before it is changed.
As addresses and constants are never changed, all items but local
variables are always live.
The region of text during which a local variable is live is
determined via the live/dead messages generated by the
Live Variable analysis phase of the Global Optimizer.
.NH 3
The allocation determination subphase
.PP
If a procedure has more items than registers,
it may be advantageous to put an item in a register
only during those parts of the procedure where it is most
heavily used.
Such a part will be called a timespan.
With every item we may associate a set of timespans.
If two timespans of an item overlap,
at most one of them may be granted a register,
as there is no use in putting the same item in two
registers simultaneously.
If two timespans of an item are distinct,
both may be chosen;
the item will possibly be put in two
different registers during different parts of the procedure.
The timespan may also consist
of the whole procedure.
.PP
A list of (item,timespan) pairs (allocations)
is build, which will be the input to the decision making
subphase of RA (packing subphase).
This allocation list is the main data structure of RA.
The description of the remainder of RA will be in terms
of allocations rather than items.
The phrase "to assign a register to an allocation" means "to assign
a register to the item of the allocation for the duration of
the timespan of the allocation".
Subsequent subphases will add more information
to this list.
.PP
Several factors must be taken into account when a
timespan for an item is constructed:
.IP 1.
At any \fIentry point\fR of the timespan where the
item is live,
the register must be initialized with the item
.IP 2.
At any exit point of the timespan where the item is live,
the item must be updated.
.LP
In order to decrease these costs, we will only consider timespans with
one entry point
and no live exit points.
.NH 3
The rivals computation subphase
.PP
As stated before, several different items may be put in the
same register, provided they are not live simultaneously.
For every allocation we determine the intersection
of its timespan and the lifetime of its item (i.e. the part of the
procedure during which the item is live).
The allocation is said to be busy during this intersection.
If two allocations are ever busy simultaneously they are
said to be rivals of each other.
The rivals information is added to the allocation list.
.NH 3
The profits computation subphase
.PP
To make good decisions, the packing subphase needs to
know which allocations can be assigned the same register
(rivals information) and how much is gained by
granting an allocation a register.
.PP
Besides the gains of using a register instead of an
item,
two kinds of overhead costs must be
taken into account:
.IP -
the register must be initialized with the item
.IP -
the register must be saved at procedure entry
and restored at procedure exit.
.LP
The latter costs should not be due to a single
allocation, as several allocations can be assigned the same register.
These costs are dealt with after packing has been done.
They do not influence the decisions of the packing algorithm,
they may only undo them.
.PP
The actual profits consist of improvements
of execution time and code size.
As the former is far more difficult to estimate , we will
discuss code size improvements first.
.PP
The gains of putting a certain item in a register
depends on how the item is used.
Suppose the item is
a pointer variable.
On machines that do not have a
double-indirect addressing mode,
two instructions are needed to dereference the variable
if it is not in a register, but only one if it is put in a register.
If the variable is not dereferenced, but simply copied, one instruction
may be sufficient in both cases.
So the gains of putting a pointer variable in a register are higher
if the variable is dereferenced often.
.PP
To make accurate estimates, detailed knowledge of
the target machine and of the code generator
would be needed.
Therefore, a simplification has been made that substantially limits
the amount of target machine information that is needed.
The estimation of the number of bytes saved does
not take into account how an item is used.
Rather, an average number is used.
So these gains are computed as follows:
.DS
#bytes_saved = #occurrences * gains_per_occurrence
.DE
The number of occurrences is derived from
the EM code.
Note that this is not exact either,
as there is no one-to-one correspondence between occurrences in
the EM code and in the assembler code.
.PP
The gains of one occurrence depend on:
.IP 1.
the type of the item
.IP 2.
the size of the item
.IP 3.
the type of the register
.LP
and for local variables and addresses of local variables:
.IP 4.
the type of the local variable
.IP 5.
the offset of the variable in the stackframe
.LP
For every allocation we try two types of registers: the register type
of the item and the general register type.
Only the type with the highest profits will subsequently be used.
This type is added to the allocation information.
.PP
To compute the gains, RA uses a machine-dependent table
that is read from a machine descriptor file.
By means of this table the number of bytes saved can be computed
as a function of the five properties.
.PP
The costs of initializing a register with an item
is determined in a similar way.
The cost of one initialization is also
obtained from the descriptor file.
Note that there can be at most one initialization for any
allocation.
.PP
To summarize, the number of bytes a certain allocation would
save is computed as follows:
.DS
net_bytes_saved = bytes_saved - init_cost
bytes_saved = #occurrences * gains_per_occ
init_cost = #initializations * costs_per_init
.DE
.PP
It is inherently more difficult to estimate the execution
time saved by putting an item in a register,
because it is impossible to predict how
many times an item will be used dynamically.
If an occurrence is part of a loop,
it may be executed many times.
If it is part of a conditional statement,
it may never be executed at all.
In the latter case, the speed of the program may even get
worse if an initialization is needed.
As a clear example, consider the piece of "C" code in Fig. 13.1.
.DS
switch(expr) {
case 1: p(); break;
case 2: p(); p(); break;
case 3: p(); break;
default: break;
}
Fig. 13.1 A "C" switch statement
.DE
Lots of bytes may be saved by putting the address of procedure p
in a register, as p is called four times (statically).
Dynamically, p will be called zero, one or two times,
depending on the value of the expression.
.PP
The optimizer uses the following strategy for optimizing
execution time:
.IP 1.
try to put items in registers during \fIloops\fR first
.IP 2.
always keep the initializing code outside the loop
.IP 3.
if an item is not used in a loop, do not put it in a register if
the initialization costs may be higher than the gains
.LP
The latter condition can be checked by determining the
minimal number of usages (dynamically) of the item during the procedure,
via a shortest path algorithm.
In the example above, this minimal number is zero, so the address of
p is not put in a register.
.PP
The costs of one occurrence is estimated as described above for the
code size.
The number of dynamic occurrences is guessed by looking at the
loop nesting level of every occurrence.
If the item is never used in a loop,
the minimal number of occurrences is used.
From these facts, the execution time improvement is assessed
for every allocation.
.NH 3
The packing subphase
.PP
The packing subphase takes as input the allocation
list and outputs a
description of which allocations should be put
in which registers.
So it is essentially the decision making part of RA.
.PP
The packing system tries to assign a register to allocations one
at a time, in some yet to be defined order.
For every allocation A, it first checks if there is a register
(of the right type)
that is already assigned to one or more allocations,
none of which are rivals of A.
In this case A is assigned the same register.
Else, A is assigned a new register, if one exists.
A table containing the number of free registers for every type
is maintained.
It is initialized with the number of non-scratch registers of
the target computer and updated whenever a
new register is handed out.
The packing algorithm stops when no more allocations can
or need be assigned a register.
.PP
After an allocation A has been packed,
all allocations with non-disjunct timespans (including
A itself) are removed from the allocation list.
.PP
In case the number of items exceeds the number of registers, it
is important to choose the most profitable allocations.
Due to the possibility of having several allocations
occupying the same register,
this problem is quite complex.
Our packing algorithm uses simple heuristic rules
and avoids any combinatorial search.
It has distinct rules for different costs measures.
.PP
If object code size is the most important factor,
the algorithm is greedy and chooses allocations in
decreasing order of their profits attribute.
It does not take into account the fact that
other allocations may be passed over because of
this decision.
.PP
If execution time is at prime stake, the algorithm
first considers allocations whose timespans consist of loops.
After all these have been packed, it considers the remaining
allocations.
Within the two subclasses, it considers allocations
with the highest profits first.
When assigning a register to an allocation with a loop
as timespan, the algorithm checks if the item has
already been put in a register during another loop.
If so, it tries to use the same register for the
new allocation.
After all packing has been done,
it checks if the item has always been assigned the same
register (although not necessarily during all loops).
If so, it tries to put the item in that register during
the entire procedure. This is possible
if the allocation (item,whole_procedure) is not a rival
of any allocation with a different item that has been
assigned to the same register.
Note that this approach is essentially 'bottom up',
as registers are first assigned over small regions
of text which are later collapsed into larger regions.
The advantage of this approach is the fact that
the decisions for one loop can be made independently
of all other loops.
.PP
After the entire packing process has been completed,
we compute for each register how much is gained in using
this register, by simply adding the net profits
of all allocations assigned to it.
This total yield should outweigh the costs of
saving/restoring the register at procedure entry/exit.
As most modern processors (e.g. 68000, Vax) have special
instructions to save/restore several registers,
the differential costs of saving one extra register are by
no means constant.
The costs are read from the machine descriptor file and
compared to the total yields of the registers.
As a consequence of this analysis, some allocations
may have their registers taken away.
.NH 3
The transformation subphase
.PP
The final subphase of RA transforms the EM text according to the
decisions made by the packing system.
It traverses the text of the currently optimized procedure and
changes all occurrences of items at points where
they are assigned a register.
It also clears the score field of the register messages for
normal local variables and emits register messages with a very
high score for the pseudo locals.
At points where registers have to be initialized with items,
it generates EM code to do so.
Finally it tries to decrease the size of the stackframe
of the procedure by looking at which local variables need not
be given memory locations.