You are on page 1of 13

University of Bonga College of Engineering

Department of Computer Science CoSc3101 – COMPILER DESIGN


Chapter 5 Handouts – Intermediate Code Generation, Code Optimization and Code generation

Intermediate Code Generation

 Translating source program into an “intermediate language”


 Simple
 CPU Independent,
 …yet, close in spirit to machine language.
 Or, depending on the application other intermediate languages may be used, but in
general, we opt for simple, well structured intermediate forms.
 (And this completes the “Front-End” of Compilation).

Benefits
1. Retargeting is facilitated
2. Machine independent Code Optimization can be applied.

Intermediate Code

 Intermediate codes are machine independent codes, but they are close to machine instructions.
 The given program in a source language is converted to an equivalent program in an
intermediate language by the intermediate code generator.
 Intermediate language can be many different languages, and the designer of the compiler
decides this intermediate language.
 Syntax trees can be used as an intermediate language.
 Postfix notation can be used as an intermediate language.
 three-address code (Quadruples) can be used as an intermediate language
 we will use quadruples to discuss intermediate code generation
 Quadruples are close to machine instructions, but they are not actual machine
instructions.

COMPILER DESIGN (CoSc3112) Page 1


 Some programming languages have well defined intermediate languages.
 java – java virtual machine
 prolog – warren abstract machine
 In fact, there are byte-code emulators to execute instructions in these
intermediate languages.
Without IR With IR

Types of Intermediate Languages

Postfix form

Example

a+b ab+
(a+b)*c ab+c*
a+b*c abc*+
a:=b*c+b*d abc*bd*+:=
 (+) simple and concise

COMPILER DESIGN (CoSc3112) Page 2


 (+) good for driving an interpreter

(- ) Not good for optimization or code generation

Three Address Code

 Statements of general form x:=y op z

 No built-up arithmetic expressions are allowed.

 As a result, x:=y + z * w should be represented as


t1:=z * w
t2:=y + t1
x:=t2

 Observe that given the syntax-tree or the dag of the graphical representation we can easily
derive a three address code for assignments as above.

 In fact three-address code is a linearization of the tree.

 Three-address code is useful: related to machine-language/ simple/ optimizable.

Example of 3-address code

 Consider the assignment a: =b*-c + b*-c:


t1:=- c
t2:=b * t1
t3:=- c
t4:=b * t3
t5:=t2 + t4
a:=t5

Types of Three-Address Statements

Assignment Statement: x:=y op z


Assignment Statement: x:=op z
Copy Statement: x:=z
Unconditional Jump: goto L
Conditional Jump: if x relop y goto L
Stack Operations: Push/pop
Implementations of 3-address statements

 Quadruples

A quadruple is:

x := y op z

COMPILER DESIGN (CoSc3112) Page 3


where x, y and z are names, constants or compiler-generated temporaries; op is any operator.

But we may also the following notation for quadruples (much better notation because it looks like a
machine code instruction)

op y,z,x

apply operator op to y and z, and store the result in x.

We use the term “three-address code” because each statement usually contains three addresses (two
for operands, one for the result).

Example:

t1:=- c
t2:=b * t1
t3:=- c
t4:=b * t3
t5:=t2 + t4
a:=t5
op arg1 arg2 result

(0) uminus c t1

(1) * b t1 t2

(2) uminus c

(3) * b t3 t4

(4) + t2 t4 t5

(5) := t5 a

Temporary names must be entered into the symbol table as they are created.

Three-Address Statements

Binary Operator:

op y,z,result or result := y op z

where op is a binary arithmetic or logical operator. This binary operator is applied to y and z, and the
result of the operation is stored in result.

Ex: add a,b,c


gt a,b,c

COMPILER DESIGN (CoSc3112) Page 4


addr a,b,c
addi a,b,c
Unary Operator:

op y,result or result := op y

where op is a unary arithmetic or logical operator. This unary operator is applied to y, and the result of
the operation is stored in result.

Ex: uminus a,c


not a,c
inttoreal a,c
Indexed Assignments:

move y[i],,x or x := y[i]


move x,,y[i] or y[i] := x

Address and Pointer Assignments:

moveaddr y,,x or x := &y


movecont y,,x or x := *y
Other types of 3-address statements

e.g. ternary operations like


x[i]:=y x:=y[i] require two or more entries. e.g.

 Triples

A triple has only three fields, which we call op, arg,, and arg2. Note that the result field in Fig. is used
primarily for temporary names. Using triples, we refer to the result of an operation x op y by its
position, rather than by an explicit temporary name. Thus, instead of the temporary t1 in Fig, a triple

COMPILER DESIGN (CoSc3112) Page 5


representation would refer to position (0). Parenthesized numbers represent pointers into the triple
structure itself. In positions or pointers to positions were called value numbers.
t1:=- c
t2:=b * t1
t3:=- c
t4:=b * t3
t5:=t2 + t4
a:=t5

op arg1 arg2

(0) uminus c

(1) * b (0)

(2) uminus c

(3) * b (2)

(4) + (1) (3)

(5) assign a (4)

Temporary names are not entered into the symbol table.

 Indirect Triples

Indirect triples consist of a listing of pointers to triples, rather than a listing of triples themselves. For
example, let us use an array instruction to list pointers to triples in the desired order. Then, the
triples in Fig. might be represented as in Fig. With indirect triples, an optimizing compiler can move
an instruction by reordering the instruction list, without affecting the triples themselves.

COMPILER DESIGN (CoSc3112) Page 6


Code Optimization: The Idea

> Transform the program to improve efficiency


> Performance: faster execution
> Size: smaller executable, smaller memory footprint

Tradeoffs:
1) Performance vs. Size
2) Compilation speed and memory
> There is no perfect optimizer

> Example: optimize for simplicity

Optimization on many levels

> Optimizations both in the optimizer and back-end

Optimizations in the Backend

> Register Allocation

> Instruction Selection

> Peep-hole Optimization

Register Allocation

> Processor has only finite amount of registers

— Can be very small (x86)

— Temporary variables

— non-overlapping temporaries can share one register

> Passing arguments via registers

> Optimizing register allocation very important for good performance

COMPILER DESIGN (CoSc3112) Page 7


— Especially on x86

Instruction Selection

> For every expression, there are many ways to realize them for a processor

> Example: Multiplication*2 can be done by bit-shift

Instruction selection is a form of optimization

Peephole Optimization

> Simple local optimization

> Look at code “through a hole”

— replace sequences by known shorter ones

— table pre-computed

Important when using simple instruction selection!

Examples for Optimizations

> Constant Folding / Propagation

> Copy Propagation

> Algebraic Simplifications

> Strength Reduction

> Dead Code Elimination

— Structure Simplifications

> Loop Optimizations

> Partial Redundancy Elimination

> Code In- lining

Constant Folding

COMPILER DESIGN (CoSc3112) Page 8


> Evaluate constant expressions at compile time

> Only possible when side-effect freeness guaranteed

Constant Propagation

> Variables that have constant value, e.g. c := 3

— Later uses of c can be replaced by the constant

— If no change of c between!

Analysis needed, as b can be assigned more than once!

Copy Propagation

> for a statement x := y

> Replace later uses of x with y, if x and y have not been changed.

Analysis needed, as y and x can be assigned more than once!

Algebraic Simplifications

> Use algebraic properties to simplify expressions

COMPILER DESIGN (CoSc3112) Page 9


Important to simplify code for later optimizations

Strength Reduction

> Replace expensive operations with simpler ones

> Example: Multiplications replaced by additions

Peephole optimizations are often strength reductions

Dead Code

> Remove unnecessary code

— e.g. variables assigned but never read

> Remove code never reached

Loop Optimizations

> Optimizing code in loops is important

— often executed, large payoff

— All optimizations help when applied to loop-bodies

> Some optimizations are loop specific

Advanced Optimizations

> Optimizing for using multiple processors

— Auto parallelization

— Very active area of research (again)

> Inter-procedural optimizations

COMPILER DESIGN (CoSc3112) Page 10


— Global view, not just one procedure

— Profile-guided optimization

> Vectorization

> Dynamic optimization

— Used in virtual machines (both hardware and language VM)

Iterative Process

> There is no general “right” order of optimizations

> One optimization generates new opportunities for a preceding one.

> Optimization is an iterative process

Code generation
 The final phase of a compiler is code generation
 It receives an intermediate representation (IR) with supplementary information in symbol table
 Produces a semantically equivalent target program
 Code generator main tasks:
o Instruction selection
o Register allocation and assignment
o Instruction ordering

Issues in the Design of Code Generator

⚫ The most important criterion is that it produces correct code

⚫ Input to the code generator

⚫ IR + Symbol table

⚫ 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

⚫ The target program

⚫ Common target architectures are: RISC, CISC and Stack based machines

COMPILER DESIGN (CoSc3112) Page 11


⚫ In this chapter we use a very simple RISC-like computer with addition of some CISC-like
addressing modes

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.

For example, every three-address statement of the form x = y + z, where x, y, and z are statically
allocated, can be translated into the code sequence

LD Ro, y // Ro = y (load y into register Ro)


ADD Ro, Ro, z // Ro = R o + z (add z t o Ro )
ST x, Ro // x = Ro (store Ro into x)

This strategy often produces redundant loads and stores. For example, the sequence of three-
address statements would be translated into
a=b+c
d=a+e

LD Ro, b // Ro = b
ADD Ro, Ro, c // Ro = Ro + c
ST a, Ro // a = Ro
LD Ro,Y a // Ro = a
ADD Ro, Ro, e // Ro = Ro + e
ST d, Ro // d = Ro

Here, the fourth statement is redundant since it loads a value that has just been stored, and so
is the third if a is not subsequently used.

The quality of the generated code is usually determined by its speed and size. On most
machines, a given IR program can be implemented by many different code sequences, with
significant cost differences between the different implementations.

Register Allocation

A key problem in code generation is deciding what values to hold in what registers.
Registers are the fastest computational unit on the target machine, but we usually do not
have enough of them to hold all values. Values not held in registers need to reside in
memory. Instructions involving register operands are invariably shorter and faster than
those involving operands in memory, so efficient utilization of registers is particularly
important.

COMPILER DESIGN (CoSc3112) Page 12


The use of registers is often subdivided into two sub problems:

1. Register allocation, during which we select the set of variables that will reside in registers
at each point in the program.
2. Register assignment, during which we pick the specific register that a variable will reside
in.
3. Complications imposed by the hardware architecture

A simple target machine model

⚫ Load operations: LD r,x and LD r1, r2


⚫ Store operations: ST x,r
⚫ Computation operations: OP dst, src1, src2
⚫ Unconditional jumps: BR L
⚫ Conditional jumps: Bcond r, L like BLTZ r, L

A Simple Code Generator

In this section, we shall consider an algorithm that generates code for a single basic block. It
considers each three-address instruction in turn, and keeps track of what values are in what
registers so it can avoid generating unnecessary loads and stores.

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 sub expression while a
larger expression is being evaluated, or more generally, a 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.

COMPILER DESIGN (CoSc3112) Page 13

You might also like