Download as ppt, pdf, or txt
Download as ppt, pdf, or txt
You are on page 1of 33

CIS 461

Compiler Design and Construction


Fall 2012

Lecture-Module 17
slides derived from Tevfik Bultan, Keith Cooper, and Linda
Torczon

1
Structure of a Compiler

Semantic Intermediate
Scanner Parser Code
Analysis
Generation
IR

Code Instruction Register Instruction


Optimization Selection Allocation Scheduling

Front end of a compiler is efficient and can be automated


Back end is generally hard to automate and finding the optimum solution requires
exponential time
Intermediate code generation can effect the performance of the back end

2
Intermediate Representations

Abstract Syntax Trees (AST) high-level


Graphical IRs Directed Acyclic Graphs (DAG)
Control Flow Graphs (CFG)
Static Single Assignment Form (SSA)
Linear IRs Stack Machine Code
Three Address Code low-level

Hybrid approaches mix graphical and linear representations


SGI and SUN compilers use three address code but provide ASTs
for loops if-statements and array references
Use three-address code in basic blocks in control flow graphs

3
Abstract Syntax Trees (ASTs)
if (x < y)
Statements
x = 5*y + 5*y/3;
else
y = 5; IfStmt AssignStmt
x = x+y;

AssignStmt AssignStmt x +
<

x y x + y x y
5

* /

5 y 3
*

5 y
4
Directed Acyclic Graphs (DAGs)
Use directed acyclic graphs to represent expressions
Use a unique node for each expression
Statements

if (x < y)
x = 5*y + 5*y/3; IfStmt AssignStmt
else
y = 5;
x = x+y; AssignStmt AssignStmt
<

x y +

/
*
3

5
5
Control Flow Graphs (CFGs)
Nodes in the control flow graph are basic blocks
A basic block is a sequence of statements always entered at the
beginning of the block and exited at the end
Edges in the control flow graph represent the control flow

if (x < y)
x = 5*y + 5*y/3; B0
if (x < y) goto B1 else goto B2
else
y = 5;
x = x+y; B1 B2
x = 5*y + 5*y/3 y = 5

B3
Each block has a sequence of statements x = x+y
No jump from or to the middle of the block
Once a block starts executing, it will execute till the end
6
Stack Machine Code pushes the value
at the location x to
if (x < y) the stack
load x
x = 5*y + 5*y/3; load y
else iflt L1 pops the top
goto L2 two elements and
y = 5; compares them
L1: push 5
x = x+y;
load y
multiply pops the top two
push 5 elements, multiplies
JVM: A stack machine them, and pushes the
load y
In the project we generate assembly code for JVM result back to the stack
multiply
which is converted to bytecode by Jasmin assembler
push 3
JVM interpreter executes the bytecode on different
divide
machines
add
JVM has an operand stack which we use to evaluate stores the value at the
store x top of the stack to the
expressions
goto L3 location x
JVM provides 65535 local variables for each method
L2: push 5
The local variables are like registers so we do not have
store y
to worry about register allocation
L3: load x
Each local variable in JVM is denoted by a number
load y
between 0 and 65535 (x and y in the example will be
add
assigned unique numbers)
store x
7
Three-Address Code
Each instruction can have at most three operands

Assignments
x := y
x := y op z op: binary arithmetic or logical operators
x := op y op: unary operators (minus, negation, integer to
float conversion)
Branch
goto L Execute the statement with labeled L next

Conditional Branch
if x relop y goto L relop: <, =, <=, >=, ==, !=
if the condition holds we execute statement labeled L next
if the condition does not hold we execute the statement following this
statement next

8
Three-Address Code
if (x < y) Variables can be represented with
x = 5*y + 5*y/3; their locations in the symbol table
else
y = 5; if x < y goto L1
goto L2
x = x+y; L1: t1 := 5 * y
t2 := 5 * y
t3 := t2 / 3
Temporaries: temporaries correspond x := t1 + t2
to the internal nodes of the syntax tree goto L3
L2: y := 5
L3: x := x + y

Three address code instructions can be represented as an array of


quadruples: operation, argument1, argument2, result
triples: operation, argument1, argument2
(each triple implicitly corresponds to a temporary)

9
Three-Address Code Generation for a Simple
Grammar
Attributes: E.place: location that holds the value of expression E
E.code: sequence of instructions that are generated for E
Procedures: newtemp(): Returns a new temporary each time it is called
gen(): Generates instruction (have to call it with appropriate arguments)
lookup(id.name): Returns the location of id from the symbol table

Productions Semantic Rules


S id := E id.place lookup(id.name);
S.code E.code || gen(id.place := E.place);
E E1 + E2 E.place newtemp();
E.code E1.code || E2.code || gen(E.place := E1.place + E2.place);
E E1 * E 2 E.place newtemp();
E.code E1.code || E2.code || gen(E.place := E1.place * E2.place);
E ( E1 ) E.code E1.code;
E.place E1.place;
E E1 E.place newtemp();
E.code E1.code || gen(E.place := uminus E1.place);
E id E.place lookup(id.name); 10
E.code (empty string)
Stack Machine Code Generation for a Simple
Grammar
Attributes: E.code: sequence of instructions that are generated for E
(no place for an expression is needed since the result of an expression
is stored in the operand stack)
Procedures: newtemp(): Returns a new temporary each time it is called
gen(): Generates instruction (have to call it with appropriate arguments)
lookup(id.name): Returns the location of id from the symbol table

Productions Semantic Rules


S id := E id.place lookup(id.name);
S.code E.code || gen(store id.place);
E E1 + E 2 E.code E1.code || E2.code || gen(add);
(arguments for the add instruction are in the top of the stack)
E E1 * E2 E.code E1.code || E2.code || gen(multiply);
E ( E1 ) E.code E1.code;
E E1 E.code E1.code || gen( negate);
E id E.code gen(load id.place)

11
Code Generation for Boolean Expressions
Two approaches
Numerical representation
Implicit representation
Numerical representation
Use 1 to represent true, use 0 to represent false
For three-address code store this result in a temporary
For stack machine code store this result in the stack
Implicit representation
For the boolean expressions which are used in flow-of-control statements
(such as if-statements, while-statements etc.) boolean expressions do not
have to explicitly compute a value, they just need to branch to the right
instruction
Generate code for boolean expressions which branch to the appropriate
instruction based on the result of the boolean expression

12
Boolean Expressions: Numerical Representation
Attributes : E.place: location that holds the value of expression E
E.code: sequence of instructions that are generated for E
id.place: location for id
Global variable: nextstat: Returns the location of the next instruction to be generated
(each call to gen() increments nextstat by 1)

Productions Semantic Rules

E id1 relop id2 E.place newtemp();


E.code gen(if id1.place relop.op id2.place goto nextstat+3);
|| gen(E.place := 0)
|| gen(goto nextstat+2)
|| gen(E.place := 1);

E E1 and E2 E.place newtemp();


E.code E1.code || E2.code
|| gen(E.place := E1.place and E2.place);

13
Boolean Expressions: Implicit Representation
Attributes : E.code: sequence of instructions that are generated for E
E.false: instruction to branch to if E evaluates to false
E.true: instruction to branch to if E evaluates to true
(E.code is synthesized whereas E.true and E.false are inherited)
id.place: location for id

can be any relational operator:


Productions Semantic Rules
==, <=, >= !=
E id1 relop id2 E.code gen(if id1.place relop.op id2.place goto E.true)
|| gen(goto E.false);

E E1 and E2 E1.true newlabel(); These places will be filled


E1.false E. false; (short-circuiting) with lables later on when
This generated label E2.true E. true; they become available
will be inserted to the place E2.false E. false;
for E1.true in the code E.code E1.code || gen(E1.true :) || E2.code ;
generated for E1 14
Example
Numerical representation:
These are the locations of
three-address code instructions, 100 if x < y goto 103
they are not labels 101 t1 := 0
102 goto 104
103 t1 := 1
104 if a = b goto 107
105 t2 := 0
106 goto 108
107 t2 := 1
108 t3 := t1 and t2
Input boolean expression:
x < y and a == b Implicit representation:
if x < y goto L1
goto LFalse
L1: if a = b goto LTrue
goto LFalse
These labels will be generated ...
later on, and will be inserted LTrue:
to the corresponding places
LFalse:
15
Flow-of-Control Statements
If-then-else
Branch based on the result of boolean expression

Pre-test
Loops
Evaluate condition before loop (if needed)
Loop body
Evaluate condition after loop
Branch back to the top if condition holds
Post-test
Merges test with last block of loop body

Next block
While, for, do, and until all fit this basic model

16
Flow-of-Control Statements: Code Structure

S if E then S1 else S2 S while E do S1

to E.true S.begin: to E.true


E.code if E evaluates to true E.code
to E.false to E.false
E.true: if E evaluates to false E.true:
S1.code S1.code

goto S.next goto S.begin


E.false: E.false:
S2.code

S.next: Another approach is to place



E.code after S1.code

17
Flow-of-Control Statements
Attributes : S.code: sequence of instructions that are generated for S
S.next: label of the instruction that will be executed immediately after S
(S.next is an inherited attribute)
Productions Semantic Rules
S if E then S1 else S2 E.true newlabel();
E.false newlabel();
S1.next S. next;
S2.next S. next;
S.code E.code || gen(E.true :) || S1.code
|| gen(goto S.next) || gen(E.false :) || S2.code ;

S while E do S1 S.begin newlabel();


E.true newlabel();
E.false S. next;
S1.next S. begin;
S.code gen(S.begin :) || E.code || gen(E.true :) || S1.code
|| gen(goto S.begin);

S S1 ; S2 S1.next newlabel();
S2.next S.next; 18
S.code S .code || gen(S .next :) || S .code
Example

L1: if a < b goto L2


Input code fragment: goto LNext
L2: if c < d goto L3
while (a < b) { goto L4
if (c < d) L3: t1 := y + z
x = y + z; x := t1
goto L1
else
L4: t2 := y z
x=yz x := t2
} goto L1
LNext: ...

19
Backpatching
E.true, E.false, S.next may not be computed in a single pass (they are
inherited attributes)
Backpatching is a technique for generating labels for E.true, E.false,
S.next and inserting them to the appropriate locations
Basic idea
Keep lists E.truelist, E.falselist, S.nextlist
E.truelist: the list of instructions where the label for E.true
have to be inserted when it becomes available
S.nextlist: the list of instructions where the label for S.next
have to be inserted when it becomes available
When labels E.true, E.false, S.next are computed these labels are
inserted to the instructions in these lists

20
Flow-of-Control Statements: Case Statements
Case Statements
1 Evaluate the controlling expression
2 Branch to the selected case
3 Execute the code for that case
4 Branch to the statement after the case
Part 2 is the key

Strategies
Linear search (nested if-then-else constructs)
Build a table of case values & binary search it
Directly compute an address (requires dense case set)
Use an array of labels that is addressed by the case value

21
Type Conversions
Mixed-type expressions
Insert conversions as needed from conversion table
i2f r1, r2 (convert the integer value in register r 1 to float, and store
the result in register r2)
Most languages have symmetric conversion tables

Typical
Addition
Table

22
Memory Layout
Placing run time data structures
S G
S
Stack and heap share free
C t l H space
t
o a&o e
d t b a
a Fixed-size areas together
c
e i a p
k For compiler, this is the
c l entire
0 high picture

Alignment and padding


Machines have alignment restrictions
32-bit floating point numbers and integers should begin on a full-word
boundary (32-bit boundary)
Place values with identical restrictions next to each other
Assign offsets from most restrictive to least
If needed, insert padding (space left unused due to alignment restrictions) to
match restrictions

23
Memory Allocation
PD
D D; D
D id : T
T char | int | float | array[num] of T | pointer T

Attributes: T.type, T.width


Basic types: char width 4, integer width 4, float width 8
Type constructors: array(size,type) width is size * (width of type)
pointer(type) width is 4

Enter the variables to the symbol table with their type and memory location
Set the type attribute T.type
Layout the storage for variables
Calculate the offset for each local variable and enter it to the symbol table
Offset can be offset from a static data area or from the beginning of the local
data area in the activation record
24
A Translation Scheme for Memory Allocation

P {offset 0;} D
D D; D
D id : T {enter(id.name,T.type, offset); offset offset + T.width; }
T char { T.type char; T.width 4; }
| int { T.type integer; T.width 14; }
| float { T.type float; T.width 8; }
| array[num] of T1 { T.type array(num.val, T1.type);
T.width num.val * T1.width; }
| pointer T { T.type pointer(T1.type); T.width 8; }

Note that if the size of the array is not a constant we cannot


compute its width at compile time
In that case allocate the memory for the array in the heap and
allocate the memory for the pointer to the heap at compile time
25
Memory Allocation for Nested Procedures

PD
D D; D
D id : T | proc id; D; S

Use two stacks:


Stack for symbol table: top(tblptr) points to current symbol table
Stack for offset: top(offset) gives the value of the offset in the
current symbol table
addwidth(table,width) stores the cumulative width of all the entries in
the symbol table in the header of the symbol table
mktbl(previous) creates a new symbol table linked to the previous
enter(table, name, type, offset) enters a variable to the symbol table
enterProc(table,name,newtable) enters a procedure to the symbol table

26
Memory Allocation for Nested Procedures
P {t mktable(nil); push(t,tblptr); push(0, offset)}
D
{addwidth(top(tblptr), top(offset)); pop(tblptr); pop(offset);}
D D; D
D proc id ;
{ t mktable(top(tblptr)); push(t,tblptr); push(0,offset); }
D;S
{ t top(tblptr); addwidth(t, top(offset)); pop(tblptr);
pop(offset);
D id : T
{enter(top(tblptr), id.name, T.type, top(offset));
top(offset) top(offset) + T.width; }

27
Array access:
How does the compiler handle A[ i ][ j ] ?
First, must agree on a storage scheme
Row-major order (most languages)
Lay out as a sequence of consecutive rows
Rightmost subscript varies fastest
A[1,1], A[1,2], A[1,3], A[2,1], A[2,2], A[2,3]
Column-major order (Fortran)
Lay out as a sequence of columns
Leftmost subscript varies fastest
A[1,1], A[2,1], A[1,2], A[2,2], A[1,3], A[2,3]
Indirection vectors (Java)
Vector of pointers to pointers to to values
Takes more space
Trades indirection for arithmetic (in modern processors memory access is
more costly than arithmetic)
Locality may not be good if indirection vectors are used

28
Laying Out Arrays
The Concept
1,1 1,2 1,3 1,4
A
2,1 2,2 2,3 2,4

Row-major order
A 1,1 1,2 1,3 1,4 2,1 2,2 2,3 2,4

Column-major order
A 1,1 2,1 1,2 2,2 1,3 2,3 1,4 2,4

Indirection vectors
These have distinct &
1,1 1,2 1,3 1,4 different cache behavior
A The order of traversal of
2,1 2,2 2,3 2,4 an array can effect the
performance

29
Base of A (starting Almost always a power of 2, known at
address of the array) compile-time use a shift for speed

Computing an Array Address


int A[1:10] low is 1
A[ i ] Make low 0 for faster
access (save a subtraction )
@A + ( i - low ) sizeof(A[1])

Expensive computation!
What about A[i1][i2] ? Lots of +, -, x operations

Row-major order, two dimensions


@A + (( i1 - low1 ) (high2 - low2 + 1) + i2 - low2) sizeof(A[1])

Column-major order, two dimensions


@A + (( i2 - low2 ) (high1 - low1 + 1) + i1 - low1) sizeof(A[1])

Indirection vectors, two dimensions


*(A[i1])[i2] where A[i1] is, itself, a 1-d array reference

30
Optimizing Address Calculation for A[ i ][ j ]
In row-major order
@A + (i-low1)(high2-low2+1) sizeof(A[1][1]) + (j - low2) sizeof(A[1][1])
Which can be factored into
@A + i (high2-low2+1) sizeof(A[1][1]) + j sizeof(A[1][1])
- (low1 (high2-low2+1) sizeof(A[1][1]) + low2 sizeof(A[1][1]))
If lowi, highi, and sizeof(A[1][1]) are known, the last term is a constant
Define @A0 as
@A - (low1 (high2-low2+1) sizeof(A[1][1]) + low2 sizeof(A[1][1]))
And len2 as (high2-low2+1)

Then, the address expression becomes


@A0 + (i len2 + j ) sizeof(A[1][1])
Compile-time constants

31
Array References
What about arrays as actual parameters?
@A
low1
Whole arrays, as call-by-reference parameters
high1
Need dimension information build a dope vector
low2
Store the values in the calling sequence high2
Pass the address of the dope vector in the parameter slot
Generate complete address polynomial at each reference

Some improvement is possible


Save leni and lowi rather than lowi and highi
Pre-compute the fixed terms in prolog sequence

What about call-by-value?


Most call-by-value languages pass arrays by reference

32
Structure of a Compiler

Semantic Intermediate
Scanner Parser Code
Analysis
Generation

Code Instruction Register Instruction


Optimization Selection Allocation Scheduling

33

You might also like