ack/doc/ego/ra/ra2

140 lines
5.7 KiB
Plaintext
Raw Normal View History

1987-03-03 10:59:52 +00:00
.NH 2
Usage of registers in ACK compilers
.PP
We will first describe the major design decisions
of the Amsterdam Compiler Kit,
as far as they concern register allocation.
Subsequently we will outline
the role of the Global Optimizer in the register
allocation process and the interface
between the code generator and the optimizer.
.NH 3
Usage of registers without the intervention of the Global Optimizer
.PP
Registers are used for two purposes:
.IP 1.
for the evaluation of arithmetic expressions
.IP 2.
to hold local variables, for the duration of the procedure they
are local to.
.LP
It is essential to note that no translation part of the compilers,
except for the code generator, knows anything at all
about the register set of the target computer.
Hence all decisions about registers are ultimately made by
the code generator.
Earlier phases of a compiler can only \fIadvise\fR the code generator.
.PP
The code generator splits the register set into two:
a fixed part for the evaluation of expressions (called \fIscratch\fR
registers) and a fixed part to store local variables.
This partitioning, which depends only on the target computer, significantly
reduces the complexity of register allocation, at the penalty
of some loss of code quality.
.PP
The code generator has some (machine-dependent) knowledge of the access costs
of memory locations and registers and of the costs of saving and
restoring registers. (Registers are always saved by the \fIcalled\fR
procedure).
This knowledge is expressed in a set of procedures for each target machine.
The code generator also knows how many registers there are and of
which type they are.
A register can be of type \fIpointer\fR, \fIfloating point\fR
or \fIgeneral\fR.
.PP
The front ends of the compilers determine which local variables may
be put in a register;
such a variable may never be accessed indirectly (i.e. through a pointer).
The front end also determines the types and sizes of these variables.
The type can be any of the register types or the type \fIloop variable\fR,
which denotes a general-typed variable that is used as loop variable
in a for-statement.
All this information is collected in a \fIregister message\fR in
the EM code.
Such a message is a pseudo EM instruction.
This message also contains a \fIscore\fR field,
indicating how desirable it is to put this variable in a register.
A front end may assign a high score to a variable if it
was declared as a register variable (which is only possible in
some languages, such as "C").
Any compiler phase before the code generator may change this score field,
if it has reason to do so.
The code generator bases its decisions on the information contained
in the register message, most notably on the score.
.PP
If the global optimizer is not used,
the score fields are set by the Peephole Optimizer.
This optimizer simply counts the number of occurrences
of every local (register) variable and adds this count
to the score provided by the front end.
In this way a simple, yet quite effective
register allocation scheme is achieved.
.NH 3
The role of the Global Optimizer
.PP
The Global Optimizer essentially tries to improve the scheme
outlined above.
It uses the following principles for this purpose:
.IP -
Entities are not always assigned a register for the duration
of an entire procedure; smaller regions of the program text
may be considered too.
.IP -
several variables may be put in the same register simultaneously,
provided at most one of them is live at any point.
.IP -
besides local variables, other entities (such as constants and addresses of
variables and procedures) may be put in a register.
.IP -
more accurate cost estimates are used.
.LP
To perform its task, the optimizer must have some
knowledge of the target machine.
.NH 3
The interface between the register allocator and the code generator
.PP
The RA phase of the optimizer must somehow be able to express its
decisions.
Such decisions may look like: 'put constant 1283 in a register from
line 12 to line 40'.
To be precise, RA must be able to tell the code generator to:
.IP -
initialize a register with some value
.IP -
update an entity from a register
.IP -
replace all occurrences of an entity in a certain region
of text by a reference to the register.
.LP
At least three problems occur here: the code generator is only used to
put local variables in registers,
it only assigns a register to a variable for the duration of an entire
procedure and it is not used to have some earlier compiler phase
make all the decisions.
.PP
All problems are solved by one mechanism, that involves no changes
to the code generator.
With every (non-scratch) register R that will be used in
a procedure P, we associate a new variable T, local to P.
The size of T is the same as the size of R.
A register message is generated for T with an exceptionally high score.
The scores of all original register messages are set to zero.
Consequently, the code generator will always assign precisely those new
variables to a register.
If the optimizer wants to put some entity, say the constant 1283, in
a register, it emits the code "T := 1283" and replaces all occurrences
of '1283' by T.
Similarly, it can put the address of a procedure in T and replace all
calls to that procedure by indirect calls.
Furthermore, it can put several different entities in T (and thus in R)
during the lifetime of P.
.PP
In principle, the code generated by the optimizer in this way would
always be valid EM code, even if the optimizer would be presented
a totally wrong description of the target computer register set.
In practice, it would be a waste of data as well as text space to
allocate memory for these new variables, as they will always be assigned
a register (in the correct order of events).
Hence, no memory locations are allocated for them.
For this reason they are called pseudo local variables.