Code Generation

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 40

CODE GENERATION

By Rupali Hande
Introduction
• The final phase of our compiler model is code
generator.
• It takes input from the intermediate
representation with supplementary
information in symbol table of the source
program and produces as output an
equivalent target program.
• Code generator main tasks:
– Instruction selection
– Register allocation and assignment
– Instruction ordering
• Instruction selection
• choose appropriate target-machine instructions to
implement the IR statements
• Register allocation and assignment
• decide what values to keep in which registers
• Instruction ordering
• decide in what order to schedule the execution of
instructions
Issues in the design of code generator
• Input to the code generator
• Target program
• Instruction selection
• Register allocation
• Choice of evaluation order
Issues in the design of code generator
• Input to the code generator
– IR + Symbol table
– IR has several choices
• Postfix notation
• Syntax tree
• Three address code
– We assume front end produces low-level IR, i.e.
values of names in it can be directly manipulated
by the machine instructions.
– Syntactic and semantic errors have been already
detected
Issues in the design of code generator
• The target program
– The output of code generator is target program.
– Output may take variety of forms
• Absolute machine language(executable code)
• Relocatable machine language(object files for linker)
• Assembly language(facilitates debugging)
– Absolute machine language has advantage that it can
be placed in a fixed location in memory and
immediately executed.
– Relocatable machine language program allows
subprograms to be compiled separately.
– Producing assembly language program as o/p makes
the process of code generation somewhat easier.
Issues in the design of code generator
• Memory management
– Mapping names in the source program to
addresses of data objects in run time memory is
done by front end & code generator.
– If a machine code is being generated, labels in
three address statements have to be converted to
addresses of instructions. This process is
analogous to “backpatching” technique.
Issues in the design of code generator
• Instruction selection
– The code generator must map the IR program into
a code sequence that can be executed by the
target machine
– The complexity of performing this mapping is
determined by factors such as
• the level of the IR
• the nature of the instruction-set architecture
• the desired quality of the generated code
Issues in the design of code generator
• Instruction selection
– Uniformity and completeness of the instruction
set are imp factors
– If we do not care about the efficiency of the target
program, instruction selection is straightforward.
– The quality of the generated code is determined
by its speed and size.
– For ex,
x=y+z

LD R0, y
ADD R0, R0, z
ST x, R0
a=b+c
d=a+e

LD R0, b
ADD R0, R0, c
ST a, R0
LD R0, a
ADD R0, R0, e
ST d, R0
Issues in the design of code generator
• Register allocation
– Instructions involving register operands are usually
shorter and faster than those involving operands
in memory.
– Two subproblems
• Register allocation: select the set of variables that will
reside in registers at each point in the program
• Register assignment: select specific register that a
variable reside in

• The multiplication instruction is of the form


M x, y
• where x, the multiplicand, is the odd register
of an even/odd register pair and y, the
multiplier, can be anywhere.
•The product occupies the entire even/odd
register pair
t=a+b
t=t*c t=a+b
T=t/d t=t+c
T=t/d

L R1, a L R0, a
A R1, b A R0, b
M R0, c A R0, c
D R0, d SRDA R0, 32
ST R1, t (shift right double arithmetic)
D R0, d
ST R1, t

Ri stands for register i. SRDA stands for Shift-Right-Double-Arithmetic and


SRDA R0,32 shifts the dividend into R1 and clears R0 so all bits equal its sign
bit. L, ST, and A stand for load, store, and add, respectively.
INC a

MOV a, R0
ADD #1,R0
MOV R0, a
Issues in the design of code generator
• Approaches to code generator
– Criterion for a code generator is to produce
correct code.
– Given the premium on correctness, designing a
code generator so it can be easily implemented,
tested, and maintained is an important design
goal.
Issues in the design of code generator
• Choice of evaluation order
– The order in which computations are performed
can affect the efficiency of the target code.
– When instructions are independent, their evaluation order
can be changed
MOV a,R0
ADD b,R0
MOV R0,t1
t1:=a+b MOV c,R1
t2:=c+d ADD d,R1
a+b-(c+d)*e MOV e,R0
t3:=e*t2
t4:=t1-t3 MUL R1,R0 MOV c,R0
MOV t1,R1 ADD d,R0
reorder SUB R0,R1 MOV e,R1
MOV R1,t4 MUL R0,R1
t2:=c+d MOV a,R0
t3:=e*t2 ADD b,R0
t1:=a+b SUB R1,R0
t4:=t1-t3 MOV R0,t4
Basic blocks and flow graphs

• Partition the intermediate code into basic blocks


– The flow of control can only enter the basic block
through the first instruction in the block. That is, there
are no jumps into the middle of the block.
– Control will leave the block without halting or
branching, except possibly at the last instruction in the
block.
• The basic blocks become the nodes of a flow
graph whose edges indicate which blocks can
follow which other blocks
• First, we determine those instructions in intermediate
code that are leaders, that is, the first instructions in
some basic block
• The rules for finding leaders are:
1. The first three-address instruction in the intermediate
code is a leader
2. Any instruction that is the target of a conditional or
unconditional jump is a leader
3. Any instruction that immediately follows a conditional or
unconditional jump is a leader
• Then, for each leader, its basic block consists of itself
and all instructions up to but not including the next
leader or the end of the intermediate program
Intermediate code to set a 10*10 matrix
to an identity matrix
Flow graph based on Basic Blocks
• instruction 1 is a leader by rule (1)
• there are three jumps, all conditional, at
instructions 9, 11, and 17
• By rule (2), the targets of these jumps are leaders;
they are instructions 3, 2, and 13, respectively
•by rule (3), each instruction following a jump is a
leader; those are instructions 10 and 12
•We conclude that the leaders are instructions 1, 2,
3, 10, 12, and 13
• The basic block of each leader contains all the
instructions from itself until just before the next
leader
Flow graph based on Basic Blocks

• The nodes of the flow graph are the basic


blocks
• There are two ways that such an edge
could be justified:
- There is a conditional or unconditional jump
from the end of B to the beginning of C
- C immediately follows B in the original order
of the three-address instructions, and B does
not end in an unconditional jump
Flow graph based on Basic Blocks
• The only successor of B1 is B2, because B1
does not end in an unconditional jump, and
the leader of B2 immediately follows the end
of B1.
• Block B3 has two successors. One is itself,
because the leader of B3, instruction 3, is the
target of the conditional jump at the end of
B3, instruction 9.
• The other successor is B4, because control
can fall through the conditional jump at the
end of B3 and next enter the leader of B4.
• Only B6 points to the exit of the flow graph,
since the only way to get to code that follows
program from which we constructed the flow
graph is to fall through the conditional jump
that ends B6.
liveness and next-use information
• We wish to determine for each three address
statement x=y+z what the next uses of x, y
and z are.
• We store this information in symbol table
• If a variable has no next-use we can reuse the register
allocated to the variable.
• We also need to know whether a variable used in a basic
block is live-on-exit, i.e. if the value contained in the
variable has a use outside the block.
• The global data-flow analysis we talked about in the
optimization unit can be used to this end.
• If no live-variable analysis has been done we assume all
variable are live on exit from the block.
• This will mean that when the end of a basic block has been
reached, all values kept only in registers will have to be
stored back into their corresponding variables’ memory
locations.
• Algorithm:
– We start at the last statement in B and scan
backwards to the beginning of B , at each
statement i:x=y+z in B we do following
• Attach to statement i the information currently found in the
symbol table regarding the next use and liveness of x, y, and
z.
• In the symbol table, set x to "not live" and "no next use.“
• In the symbol table, set y and z to "live" and the next uses of
y and z to i.
A SIMPLE CODE GENERATOR
• One of the primary issues during code generation is deciding how
to use registers to best advantage
• There are four principal uses of registers:

– In most machine architectures, some or all of the operands of an


operation must be in registers in order to perform the operation.
– Registers make good temporaries- places to hold the result of a
subexpression while a larger expression is being evaluated, or more
generally, place to hold a variable that is used only within a single basic
block
– Registers are used to hold (global) values that are computed in one basic
block and used in other blocks, for example, a loop index that is
incremented going around the loop and is used several times within the
loop.
– Registers are often used to help with run-time storage management, for
example, to manage the run-time stack, including the maintenance of
stack pointers and possibly the top elements of the stack itself.
A SIMPLE CODE GENERATOR
• The machine instructions are of the form
– LD reg, mem
– ST mem, reg
– OP reg, reg, reg
• The desired data structure has the following descriptors:
– For each available register, a register descriptor keeps track of the
variable names whose current value is in that register. Since we shall use
only those registers that are available for local use within a basic block,
we assume that initially, all register descriptors are empty
– For each program variable, an address descriptor keeps track of the
location or locations where the current value of that variable can be
found. The location might be a register, a memory address, a stack
location, or some set of more than one of these. The information can be
stored in symbol-table entry for that variable name.
The Code-Generation Algorithm
• An essential part of the algorithm is a function
getReg(I), which selects registers for each
memory location associated with the
three-address instruction I
The Code-Generation Algorithm
Machine Instructions for Operations
• For a three-address instruction such as x = y + z, do the following:
1. Use getReg(x = y + z) to select registers for x, y, and z. Call these Rx,
Ry, and Rz.

2. If y is not in Ry (according to the register descriptor for Ry), then issue an


instruction
LD Ry, y’, where y’ is one of the memory locations for y (according to the
address descriptor for y).

3. Similarly, if z is not in Rz, issue an instruction LD Rz,z’, where z’ is a


location for z.

4. Issue the instruction ADD Rx,Ry,Rz


The Code-Generation Algorithm
Machine Instructions for Copy Statements
• There is an important special case: a three-address
copy statement of the form x = y.
• We assume that getReg will always choose the same
register for both x and y.
• If y is not already in that register Ry, then generate the
machine instruction LD Ry, y.
• If y was already in Ry, we do nothing.
• It is only necessary that we adjust the register
descriptor for Ry so that it includes x as one of the
values found there.
The Code-Generation Algorithm
• Ending the Basic Block
– If the variable is a temporary used only within the
block, that is fine; when the block ends, we can
forget about the value of the temporary and
assume its register is empty.
– However, if the variable is live on exit from the
block, or if we don't know which variables are live
on exit, then we need to assume that the value of
the variable is needed later.
The Code-Generation Algorithm
Managing Register and Address Descriptors
As the code-generation algorithm issues load, store, and other machine instructions, it needs
to update the register and address descriptors. The rules are as follows:
1. For the instruction LD R, x
(a) Change the register descriptor for register R so it holds only x.
(b) Change the address descriptor for x by adding register R as an additional
location.
2. For the instruction ST x,R, change the address descriptor for x to include its own memory
location.
3. For an operation such as ADD Rx,Ry,Rz implementing a three-address instruction
x=y+z
(a) Change the register descriptor for Rx so that it holds only x.
(b) Change the address descriptor for x so that its only location is Rx. Note that the memory location
for x is not now in the address descriptor for x.
(c) Remove Rx from the address descriptor of any variable other than x.
4. When we process a copy statement x = y, after generating the load for y into register Ry, if
needed, and after managing descriptors as for all load statements (per rule 1):
(a) Add x to the register descriptor for Ry.
(b) Change the address descriptor for x so that its only location is Ry.
Example:
• Let us translate the basic block consisting of
the three-address statements
t=a-b
u=a-c
v=t+u
a=d
d=v+u
Design of the Function getReg
The rules are as follows:
1. If y is currently in a register, pick a register already
containing y as Ry. Do not issue a machine instruction to
load this register, as none is needed.
2. If y is not in a register, but there is a register that is
currently empty, pick one such register as Ry.
3. The difficult case occurs when y is not in a register, and
there is no register that is currently empty. We need to
pick one of the allowable registers anyway, and we need
to make it safe to reuse. Let R be a candidate register,
and suppose v is one of the variables that the register
descriptor for R says is in R.
• We need to make sure that v's value either is not really needed, or that there is
somewhere else we can go to get the value of v.
The possibilities are:
(a) If the address descriptor for v says that v is somewhere besides R, then we are OK.
(b) If v is x, the variable being computed by instruction I, and x is not also one of the
other operands of instruction I (z in this example), then we are OK. The reason is that in
this case, we know this value of x is never again going to be used, so we are free to
ignore it.
(c) Otherwise, if v is not used later then we are OK.
(d) If we are not OK by one of the first three cases, then we need to generate the store
instruction ST v,R to place a copy of v in its own memory location. This operation is called
a spill.
Since R may hold several variables at the moment, we repeat the above steps for each
such variable v. At the end, R's \score" is the number of store instructions we needed to
generate. Pick one of the registers with the lowest score.
• Now, consider the selection of the register Rx. The issues and options are
almost as for y, so we shall only mention the differences.
1. Since a new value of x is being computed, a register that holds only x is
always an acceptable choice for Rx. This statement holds even if x is one
of y and z, since our machine instructions allows two registers to be the
same in one instruction.
2. If y is not used after instruction I, in the sense described for variable v in
item (3c), and Ry holds only y after being loaded, if necessary, then Ry
can also be used as Rx. A similar option holds regarding z and Rz.

The last matter to consider specially is the case when I is a copy instruction
x = y. We pick the register Ry as above. Then, we always choose Rx = Ry.

You might also like