Sunteți pe pagina 1din 7

FIRST & FOLLOW the actual machine code from the parse stack, but it is useful for

The construction of a predictive parser is aided by two functions us to separate out the various tasks, such as translating high-
associated with a grammar G. These functions, FIRST and level language constructs into low-level statements, choosing
FOLLOW, allow us to fill in the entries of a predictive parsing appropriate instructions and machine idioms, optimization, and
table for G, whenever possible. Sets of tokens yielded by the register allocation. Thus, our first task is to define an IC
FOLLOW function can also be used as synchronizing tokens language and develop a translation schemes for C-language
during panic-mode error recovery. features such a control structures and Boolean expressions.
FIRST( Syntax-Directed Translation into Three-Address Code
If α is any string of grammar symbols, let FIRST(α ) be the • Compiler makes up temporary names (t1, t2, etc.) for interior
set of terminals that begin the strings derived from α . If nodes of syntax tree.
α ⇒ ε then ε is also in FIRST(α ). • Add some attributes to nodes to support code generation
To compute FIRST(X) for all grammar symbols X, apply the – place : variable name (x, y, t1, etc.)
following rules until no more terminals or ε can be added to – Code: generated code
any FIRST set: • Add semantic rules (actions) to yacc (CUP) grammar rules:
1. If X is terminal, then FIRST(X) is {X}. S -> id := E S.code := E.code || gen( id.place ':= ' E.place)
2. If X → ε is a production, then add ε to FIRST(X). E -> E1 + E2 E.place := newtmp; // returns t1, t2, etc.
3. If X is nonterminal and X →Y1 Y2 ... Yk. is a production, E.code := E1.code || E2.code ||
then place a in FIRST(X) if for some i, a is in gen(E.place ':= ' E1.place '+' E2.place)
E -> - E1 E.place := newtmp;
FIRST(Yi), and ε is in all of FIRST(Y1), ... , FIRST(Yi-1);
E.code := E1.code ||
that is, Y1, ... ,Yi-1 ⇒ ε . If ε is in FIRST(Yj) for all j = 1, 2,
gen(E.place ':= 'uminus' E1.place)
... , k, then add ε to FIRST(X). For example, everything in E -> ( E1 ) E.place := E1.place; E.code := E1.code
FIRST(Y1) is surely in FIRST(X). If Y1 does not derive ε , E -> id E.place := id.place ; E.code := ' '
then we add nothing more to FIRST(X), but if Y1⇒ ε , then Compiling Loops
we add FIRST(Y2) and so on. S -> while E do S1 S.begin := newlabel;
Now, we can compute FIRST for any string X1X2 . . . Xn as S.after := newlabel;
follows. Add to FIRST(X1X2 ... Xn) all the non-ε symbols of S.code := gen(S.begin ':') ||
FIRST(X1). Also add the non-ε symbols of FIRST(X2) if E.code ||
ε is in FIRST(X1), the non-ε symbols of FIRST(X3) if gen('if' E.place '= 0 goto' S.after) ||
ε is in both FIRST(X1) and FIRST(X2), and so on. Finally, S1.code ||
add ε to FIRST(X1X2 ... Xn) if, for all i, FIRST(Xi) contains gen('goto' S.begin) ||
ε. gen(S.after ':')
FOLLOW(A) Assignment statements
Define FOLLOW(A), for nonterminal A, to be the set of S -> id := E { p := lookup (id.name); // get address: t1,
terminals a that can appear immediately to the right of A in etc.
some sentential form, that is, the set of terminals a such that if p != nil then emit (p ':=' E.place)
there exists a derivation of the form S⇒α Α aβ for some else error }
α and β . Note that there may, at some time during the E -> E1 + E2 { E.place := newtmp; // returns t1, t2, etc.
derivation, have been symbols between A and a, but if so, they emit(E.place ':= ' E1.place '+' E2.place) }
derived ε and disappeared. If A can be the rightmost symbol E -> - E1 { E.place := newtmp;
in some sentential form, then $, representing the input right emit(E.place ':= 'uminus' E1.place) }
endmarker, is in FOLLOW(A). E -> ( E1 ) { E.place := E1.place; }
To compute FOLLOW(A) for all nonterminals A, apply the E -> id { p := lookup (id.name);
following rules until nothing can be added to any FOLLOW set: if p != nil then E.place := p
1. Place $ in FOLLOW(S), where S is the start symbol and $ is else error }
the input right endmarker. Numerical Representation
2. If there is a production A ⇒ α Β β , then everything in E ->E1 or E2 { E.place := newtmp;
FIRST(β ), except for ε , is placed in FOLLOW(B). emit(E.place ':= ' E1.place 'or' E2.place) }
3. If there is a production A ⇒ α Β , or a production A E ->E1 and E2 { E.place := newtmp;
emit(E.place ':= ' E1.place 'and'
⇒ α Β β where FIRST(β ) contains ε (i.e., β ⇒ ε ) , then
everything in FOLLOW(A) is in FOLLOW(B). E2.place) }
Intermediate Code E -> not E1 { E.place := newtmp;
Intermediate code (IC) is a language very close to assembly emit(E.place ':= ' 'not' E1.place) }
language in that it typically performs only one operation per E -> ( E1 ) { E.place := E1.place; }
instruction, however, it is different in that it uses variables E -> true { E.place := newtmp; emit(E.place ':= 1' )}
instead of registers and we do not worry about whether E -> false { E.place := newtmp; emit(E.place ':= 0' )}
operands are constants or variables (both issues are important in E -> id1 relop id2 { E.place := newtmp;
assembly languages). A real compiler would usually generate emit( 'if' id1.place relop.op id2.place
'goto' nexstat + 3);
emit(E.place ':= 0');
emit( 'goto' nexstat + 2);
emit(E.place ':= 1');}

Flow-of-Control Statements
Consider the grammar
S -> if E then S1| if E then S1 else S2| while E do S1
S -> if E then S1 { E.true := newlabel;
E.false := S.next;
S1 .next := S.next;
S.code := E.code || gen (E.true ':' ) S1.code
}
S _ if E then S1 else S2 { E.true := newlabel;
E.false := newlabel; 3-Address Instructions for Copy and Jumps
S1 .next := S.next; Move or copy instruction is of the form:
S2.next := S.next; x := y
S.code := E.code || Unconditional jump and label instructions are of the form:
gen (E.true ':' ) S1 .code || GOTO Ln Ln is a label identified by a number n
gen ('goto' S.next ) || LABEL Ln
gen (E.false ':' ) S2 .code } A label is not an instruction; it is the address of the following
S -> while E do S1 { S.begin := newlabel; instruction
E.true := newlabel; Conditional branch instructions are of the form:
E.false := S.next; IF x goto Ln Branch if x is true
S1 .next := S.begin; IFNOT x goto Ln Branch if x is false
S.code := gen (S.begin ':' ) || E.code || x is a Boolean variable that evaluate to true or false
gen (E.true ':' ) || S1 .code || The general conditional branch instruction is of the form:
gen ('goto' S.begin) if x relop y goto Ln
} Conditional branches are used to implement conditional and
Control flow translation loop statements
E -> E1 or E2 { E1.true := E.true; Backpatching Algorithms: They perform three operations
E1.false := newlabel 1) makelist(i) – creates a new list containing only i, an
E2.true := E.true; index into the array of quadruples and returns pointer to
E2.false := E.false; the list it has made.
E.code := E1.code || gen (E1.false ':' ) || E2.code } 2) merge(i,j) – concatenates the lists pointed to by i and
E -> E1 and E2 { E1.true := newlabel; j ,and returns a pointer to the concatenated list.
E1.false := E.false 3) backpatch(p,i) – inserts i as the target label for each of
E2.true := E.true; the statements on the list pointed to by p.
E2.false := E.false; The Translation scheme is as follows:-
E.code := E1.code || gen (E1.true ':' ) || E2.code } 1) E ---> E1 or M E2
E -> not E1 { E1.true := E.false; backpatch(E1.falselist, M.quad)
E1.false := E.true E.truelist = merge(E1.truelist, E2.truelist)
E.code := E1.code; } E.falselist = E2.falselist
E -> ( E1 ) {E1.true := E.true; 2) E ---> E1 and M E2
E1.false := E.false backpatch(E1.truelist, M.quad)
E.code := E1.code;} E.truelist = E2.truelist
E -> true { E.code := gen ( 'goto' ) || E.true } E.falselist = merge(E1.falselist, E2.falselist)
E -> false { E.code := gen ( 'goto' ) || E.false} 3) E ----> not E1
E -> id1 relop id2 { gen ( 'if' id1.place relop.op E.truelist = E1.falselist
id2.place E.falselist = E1.truelist
'goto' E.true) || 4) E -----> (E1)
gen ( 'goto' E.false) } E.truelist = E1.truelist
E.falselist = E1.falselist
5) E -----> id1 relop id2
E.truelist = makelist(nextquad)
E.falselist = makelist(nextquad +1 )
emit(if id1.place relop id2.place goto __ )
emit(goto ___)
6) E -----> true operands (such as y, z) have already had their
E.truelist = makelist(nextquad) definitions moved to the pre-header.
emit(goto ___) Note:
7) E -----> false 4. When applying loop-invariant code motion to
E.falselist = makelist(nextquad) nested loops, work from the innermost loop
emit(goto ___) outwards.
8) M -----> epsilon Code hoisting
M.Quad = nextquad Moving computations outside loops saves computing time
Dead-Code Elimination In the following example (2.0 * PI) is an invariant expression
A variable is live at a pt in a pgm if its value can b used there is no reason to re-compute it 100 times.
subsequently; otherwise, it is dead at tht point. A related idea is DO I = 1, 100
dead code -statements that compute values that never get used. ARRAY(I) = 2.0 * PI * I
While the programmer is unlikely to introduce any dead code ENDDO
intentionally, it may appear as result of previous By introducing a temporary variable’t’ it can be transformed to:
transformations. t = 2.0 * PI
Common Sub expression Elimination DO I = 1, 100
c=a+b ARRAY(I) = t * I
d=m*n END DO
e=b+d Induction variable reduction
f=a+b  Optimize the SSA graph rather than handling
g=-b CFG directly
h=b+a  SSA graph clarify the link from data use to its
a=j+a definition
k=m*n  Induction variable value is increased/decreased
j=b+d by constant in each iteration.
a=-b  I0 – first entry value
if m * n go to L
 I2 – value after going through loop
After CSE: t1 = a + b
 RC – loop invariant expression
c = t1
Global Value numbering (GVN)
t2 = m * n
d = t2  Symbolic evaluation (not run-time evaluation),
t3 = b + d if symbolic number is same, two computation
e = t3 are equal.
f = t1  Compiler optimization based on the SSA IR
g = -b  Build value graph from the SSA form
h = t1 /* commutative */  prevent the false variable name-value name
a=j+a mappings
k = t2  more powerful that global common sub
j = t3 expression (CSE) in some cases
a = -b Storage Organization
if t2 go to L From the perspective of the compiler writer, the executing
Loop-Invariant Code Motion target program runs in its own logical address space in which
An instruction is loop-invariant if, for each operand: each program value has a location. The management and
1. The operand is constant, OR organization of this logical address space is shared between the
2. All definitions of that operand that reach the instruction are compiler, operating system, and target machine. The operating
outside the loop, OR system maps the logical addresses into physical addresses,
3. There is exactly one in-loop definition of the operand that which are usually spread throughout memory. The run-time
reaches the instruction, and that definition is loop invariant representation of an object program in the logical address space
Algorithm sketch: consists of data and program areas as shown in Fig. A compiler
1. Find all loop-invariant instructions for a language like C++ on an operating system like Linux
2. For each instruction i: x=y+z found in step 1, might subdivide memory in this way.
check
i. that its block dominates all exits of the loop
ii. that x is not defined anywhere else in the loop
iii. that all uses of x in the loop can be reached only by i
(i.e. its block dominates all uses of x)
3. Move each instruction i that satisfies the
requirements in step 2 to a newly created pre-
header of the loop, making certain that any
Register allocation
 Two subproblems
 Register allocation: selecting the set of
variables that will reside in registers at each point in the
program
 Resister assignment: selecting specific register
that a variable reside in
 Complications imposed by the hardware architecture
 Example: register pairs for multiplication and
division
t=a+b
Many compilers use some combination of the following two t=a+b t=t+c
strategies for dynamic storage allocation: t=t*c T=t/d
1. Stack storage. Names local to a procedure are allocated space T=t/d L R0, a
on a stack. The stack supports the normal call/return policy for L R1, a A R0, b
procedures. A R1, b M R0, c
2. Heap storage. Data that may outlive the call to the procedure M R0, c SRDA R0, 32
that created it is usually allocated on a "heap" of reusable D R0, d D R0, d
storage. The heap is an area of virtual memory that allows ST R1, t ST R1, t
objects or other data elements to obtain storage when they are
A simple target machine model
created and to return that storage when they are invalidated.
To support heap management, "garbage collection" enables the  Load operations: LD r,x and LD r1, r2
run-time system to detect useless data elements and reuse their  Store operations: ST x,r
storage, even if the programmer does not return their space  Computation operations: OP dst, src1, src2
explicitly. Automatic garbage collection is an essential feature  Unconditional jumps: BR L
of many modern languages, despite it being a difficult operation  Conditional jumps: Bcond r, L like BLTZ r, L
to do efficiently; it may not even be possible for some Addressing Modes
languages.  variable name: x
 indexed address: a(r) like LD R1, a(R2) means
R1=contents(a+contents(R2))
 integer indexed by a register : like LD R1, 100(R2)
Issues in the Design of Code Generator  Indirect addressing mode: *r and *100(r)
 The most important criterion is that it produces correct  immediate constant addressing mode: like LD R1, #100
code b = a [i]
 Input to the code generator LD R1, i //R1 = i
 IR + Symbol table MUL R1, R1, 8 //R1 = Rl * 8
 We assume front end produces low-level IR, LD R2, a(R1) //R2=contents(a+contents(R1))
i.e. values of names in it can be directly manipulated by the ST b, R2 //b = R2
machine instructions. x=*p
 Syntactic and semantic errors have been LD R1, p //R1 = p
already detected LD R2, 0(R1) // R2 = contents(0+contents(R1))
 The target program ST x, R2 // x=R2
 Common target architectures are: RISC, CISC conditional-jump three-address instruction
and Stack based machines If x<y goto L
 In this chapter we use a very simple RISC-like LD R1, x // R1 = x
computer with addition of some CISC-like addressing modes LD R2, y // R2 = y
complexity of mapping SUB R1, R1, R2 // R1 = R1 - R2
 the level of the IR BLTZ R1, M // i f R1 < 0 jump t o M
costs associated with the addressing modes
 the nature of the instruction-set architecture
 LD R0, R1 cost = 1
 the desired quality of the generated code.
 LD R0, M cost = 2
a=b+c
x=y+z d=a+e  LD R1, *100(R2) cost = 3
LD R0, b Addresses in the Target Code
ADD R0, R0, c  A statically determined area Code
LD R0, y ST a, R0  A statically determined data area Static
ADD R0, R0, z LD R0, a  A dynamically managed area Heap
ST x, R0 ADD R0, R0, e  A dynamically managed area Stack
ST d, R0 liveness and next-use information
 We wish to determine for each three address statement  Any use of a variable must follow all previous
x=y+z what the next uses of x, y and z are. (according to the original block) procedure calls or
 Algorithm: indirect assignments through a pointer.
 Attach to statement i the information currently  Any procedure call or indirect assignment through a
found in the symbol table regarding the next use and liveness pointer must follow all previous (according to the
of x, y, and z. original block) evaluations of any variable.
 In the symbol table, set x to "not live" and "no Characteristic of peephole optimizations
next use.“  Redundant-instruction elimination
 In the symbol table, set y and z to "live" and  Flow-of-control optimizations
the next uses of y and z to i.  Algebraic simplifications
DAG representation of basic blocks  Use of machine idioms
 There is a node in the DAG for each of the initial Redundant-instruction elimination
values of the variables appearing in the basic block.  LD a, R0
 There is a node N associated with each statement s ST R0, a
within the block. The children of N are those nodes  if debug == 1 goto L1
corresponding to statements that are the last definitions, goto L2
prior to s, of the operands used by s. L I : print debugging information
 Node N is labeled by the operator applied at s, and also L2:
attached to N is the list of variables for which it is the Flow-of-control optimizations
last definition within the block. goto L1
 Certain nodes are designated output nodes. These are ...
the nodes whose variables are live on exit from the Ll: goto L2
block. Can be replaced by:
goto L2
...
Ll: goto L2

if a<b goto L1
...
array accesses in a DAG Ll: goto L2
 An assignment from an array, like x = a [i], is Can be replaced by:
represented by creating a node with operator =[] and if a<b goto L2
two children representing the initial value of the array, ...
a0 in this case, and the index i. Variable x becomes a Ll: goto L2
label of this new node. Algebraic simplifications
 An assignment to an array, like a [j] = y, is represented  x=x+0
by a new node with operator []= and three children  x=x*1
representing a0, j and y. There is no variable labeling Register Allocation and Assignment
this node. What is different is that the creation of this  Global Register Allocation
node kills all currently constructed nodes whose value  Usage Counts
depends on a0. A node that has been killed cannot  Register Assignment for Outer Loops
receive any more labels; i.e., it cannot become a  Register Allocation by Graph Coloring
common sub expression. Global register allocation
Rules for reconstructing the basic block from a DAG  Previously explained algorithm does local (block
 The order of instructions must respect the order of based) register allocation
nodes in the DAG. That is, we cannot compute a node's  This resulted that all live variables be stored at the end
value until we have computed a value for each of its of block
children.  To save some of these stores and their corresponding
 Assignments to an array must follow all previous loads, we might arrange to assign registers to
assignments to, or evaluations from, the same array, frequently used variables and keep these registers
according to the order of these instructions in the consistent across block boundaries (globally)
original basic block.  Some options are:
 Evaluations of array elements must follow any previous  Keep values of variables used in loops inside
(according to the original block) assignments to the registers
same array. The only permutation allowed is that two  Use graph coloring approach for more globally
evaluations from the same array may be done in either allocation
order, as long as neither crosses over an assignment to Usage counts
that array.
 For the loops we can approximate the saving by 1. To generate machine code for an interior node with
register allocation as: label k and two children with equal labels (which must
 Sum over all blocks (B) in a loop (L) be k - l) do the following:
 For each uses of x before any definition in the  Recursively generate code for the right child,
block we add one unit of saving using base b+1. The result of the right child
 If x is live on exit from B and is assigned a appears in register Rb+k.
value in B, then we ass 2 units of saving  Recursively generate code for the left child,
Flow graph of an inner loop using base b; the result appears in Rb+k-1.
 Generate the instruction OP Rb+k, Rb+k-1,
Rb+k, where OP is the appropriate operation
for the interior node in question.
2. Suppose we have an interior node with label k and
children with unequal labels. Then one of the children,
which we'll call the "big" child, has label k , and the
other child, the "little" child, has some label m < k. Do
the following to generate code for this interior node,
using base b:
 Recursively generate code for the big child,
using base b; the result appears in register
Rb+k-l.
 Recursively generate code for the small child,
using base b; the result appears in register
Code sequence using global register assignment Rb+m-l. Note that since m < k, neither Rb+k-l
nor any higher-numbered register is used.
 Generate the instruction OP Rb+k-l, Rb+m-l,
Rb+k-1 or the instruction OP Rb+k-l, Rb+k-l,
Rb+m+l, depending on whether the big child is
the right or left child, respectively.
3. For a leaf representing operand x, if the base is b
generate the instruction LD Rb, x.
Tail-Call
void f(int x) {
...
g(x);
(return;)
}
Tail-Recursion
void f(int x) {
Register allocation by Graph coloring
...
Two passes are used
f(x);
 Target-machine instructions are selected as (return;)
though there are an infinite number of }
symbolic registers Effect of tail-recursion
 Assign physical registers to symbolic ones  eliminate procedure-call overhead
 Create a register-interference graph  enable loop optimization
 Nodes are symbolic registers and edges void insert_node(int n, struct node *l) {
connect two nodes if one is live at a point where the other is if(n > l→values)
defined. if(l→next==null) make_node(l,n);
 For example in the previous example an else insert_node(n,l→next);
edge connects a and d in the graph }
 Use a graph coloring algorithm to assign After tail rec:
registers. void insert_node(int n, struct node *l) {
Loop:
if(n > l→values)
Generating code from a labeled expression tree if(l→next==null) make_node(p,n);
else { l = l→next; goto Loop; }
}
 Two problems of high level implementation
1. Branch into the body of the other procedure
2. Local scope of parameters
Leaf-Routine Optimization
Leaf routine
 leaf node in the call graph of a program
 routine that calls no procedures
 many procedures are leaf routines
 leaf routine optimization
 simplify the way parameters are passed
 remove procedure prologue / epilogue
 highly desirable with little effort
Shrink Wrapping
 moving prologue and epilogue code to enclose
the minimal part of the procedure
 inside a loop
 making many copies of codes

S-ar putea să vă placă și