cd_3rd unit _15
cd_3rd unit _15
• Intermediate code is the interface between front end and back end in a
compiler
• Ideally the details of source language are confined to the front end and
the details of target machines to the back end (a m*n model)
• In this chapter we study intermediate representations, static type
checking and intermediate code generation
1)DAG
A directed acyclic graph ( DAG) for an expression identifies the
common subexpressions (subexpressions that occur more than once) of the
expression .
• It is sometimes beneficial to crate a DAG instead of tree for Expressions.
• This way we can easily show the common sub-expressions and then use
that knowledge during code generation
• Example: a+a*(b-c)+(b-c)*d
+
+ *
*
d
a -
b c
DAG
• The leaf for ‘a’ has two parents, because ‘a’ appears twice in the
expression. More interestingly, the two occurrences of the common
subexpression b-c are represented by one node, the node labeled -.
That node has two parents, representing its two uses in the
subexpressions a*(b-c) and (b-c)*d. Even though b and c appear
twice in the complete expression, their nodes each have one parent,
since both uses are in the common subexpression b-c
SDD for creating DAG’s
= id To entry for i
num 10
+ + 1 2
3 1 3
i 10
+
t1 = b – c
+ * t2 = a * t1
t3 = a + t2
* t4 = t1 * d
d
t5 = t3 + t4
a -
b c
• A quadruple (or just "quad') has four fields, which we call op, arg,, arg2, and
result. The op field contains an internal code for the operator. For instance, the
three-address instruction x = y +x is represented by placing + in op, y in arg,, 2 in
argz, and x in result.
• The following are some exceptions to this rule:
• I. Instructions with unary operators like x = minusy or x = y do not use arg,. Note
that for a copy statement like x = y, op is =, while for most other operations, the
assignment operator is implied.
• 2. Operators like param use neither arg2 nor result.
• 3. Conditional and unconditional jumps put the target label in result
2)TRIPLE
• A triple has only three fields, which we call op, arg,, and arg2. Note
that the result field in Fig. (b) 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 tl in Fig. (b) , a triple representation would refer to
position (0). Parenthesized numbers represent pointers into the triple
structure itself.
• 3)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.
Example Three address code
t1 = minus c
t2 = b * t1
• b * minus c + b * minus c t3 = minus c
t4 = b * t3
t5 = t2 + t4
a = t5
1)Type Expressions
Example: int[2][3]
array(2,array(3,integer))
•
TYPE CHECKING
• Rules for Type Checking
• Type checking can take on two forms:
• synthesis and inference. Type synthesis builds up the type of an
expression from the types of its subexpressions. It requires names to
be declared before they are used. The type of El + E2 is defined in
terms of the types of El and E2. A typical rule for type synthesis has
the form
• if f has type s -> t and x has type s,
• then expression f (x) has type t
• Here, f and x denote expressions, and s -> t denotes a function from s
to t. This rule for functions with one argument carries over to
functions with several arguments. The rule can be adapted for El +
E2 by viewing it as a function application add(E1 , E2) .
• Type inference determines the type of a language construct from the
way it is used.
TYPE CHECKING
Conversions between
primitive types in Java
TYPE CHECKING
TYPE CHECKING
Flow-of-Control Statements
• The labels for the jumps in B.code and S.code are managed using
inherited attributes. With a boolean expression B, we associate two
labels: B.true, the label to which control flows if B is true, and B.false,
the label to which control flows if B is false. With a statement S, we
associate an inherited attribute S.next denoting a label for the
instruction immediately after the code for S. In some cases, the
instruction immediately following S.code is a jump to some label L. A
jump to a jump to L from within S.code is avoided using S.next.
Flow-of-Control
Statements
FIG 6.3 Code for if-, if-else-, and while-statements
Syntax-directed definition for Flow-
of-Control Statements
Control-Flow Translation of Boolean Expressions
•
Translation of a switch-statement
The "switch" or "case" statement is available in a variety of
languages. Our switch-statement syntax is shown in Fig.
6.48. There is a selector expression E, which is to be
evaluated, followed by n constant values Vl , V2, . . - , Vn
that the expression might take, perhaps including a default
"value," which always matches the expression if no other
value does .
Translation of Switch-Statements
D ->define T id ( F ) { S }
F -> ε | T id,F
S -> return E ;
E ->id(A)
A -> ε |E,A
Figure 6.52: Adding functions to the source language
Syntax-Directed Definitions
• Syntax-Directed Definitions
• A syntax-directed definition (SDD) is a context-free grammar together
with, attributes and rules. Attributes are associated with grammar
symbols and rules are associated with productions.
• If X is a symbol and a is one of its attributes, then we write X.a to
denote the value of a at a particular parse-tree node labeled X.
• Inherited and Synthesized Attributes
• 1. A synthesized attribute for a nonterminal A at a parse-tree node N
is defined by a semantic rule associated with the production at N.
Note that the production must have A as its head. A synthesized
attribute at node N is defined only in terms of attribute values at the
children of N and at N itself.
Syntax Directed Translation
1) L -> E n {print(E.val);}
2) E -> E1 + T {E.val=E1.val+T.val;}
3) E -> T {E.val = T.val;}
4) T -> T1 * F {T.val=T1.val*F.val;}
5) T -> F {T.val=F.val;}
6) F -> (E) {F.val=E.val;}
7) F -> digit {F.val=digit.lexval;}
Parse-Stack implementation of
postfix SDT’s
• In a shift-reduce parser we can easily implement semantic action
using the parser stack
• For each nonterminal (or state) on the stack we can associate a record
holding its attributes
• Then in a reduction step we can execute the semantic action at the
end of a production to evaluate the attribute(s) of the non-terminal at
the leftside of the production
• And put the value on the stack in replace of the rightside of
production
Example
L -> E n {print(stack[top-1].val);
top=top-1;}
E -> E1 + T {stack[top-2].val=stack[top-2].val+stack.val;
top=top-2;}
E -> T
T -> T1 * F {stack[top-2].val=stack[top-2].val+stack.val;
top=top-2;}
T -> F
F -> (E) {stack[top-2].val=stack[top-1].val
top=top-2;}
F -> digit
SDT’s with actions inside productions
• For a production B->X {a} Y
– If the parse is bottom-up then we perform
action “a” as soon as this occurrence of X 1) L -> E n
appears on the top of the parser stack 2) E -> {print(‘+’);} E1 + T
– If the parser is top down we perform “a” just 3) E -> T
before we expand Y 4) T -> {print(‘*’);} T1 * F
• Sometimes we can’t do things as easily as 5) T -> F
explained above 6) F -> (E)
7) F -> digit {print(digit.lexval);}
• One example is when we are parsing this
SDT with a bottom-up parser
SDT’s with actions inside productions (cont)
L
{print(3);}
digit
Infix to prefix translation during parsing
L
{print(‘+’);}
E + T
T F
{print(4);}
{print(‘*’);} T * F digit
{print(5);}
F digit
{print(3);}
digit
SDT’s for L-Attributed definitions
• We can convert an L-attributed SDD into an SDT using following two
rules:
– Embed the action that computes the inherited attributes for a nonterminal A
immediately before that occurrence of A. if several inherited attributes of A
are dependent on one another in an acyclic fashion, order them so that those
needed first are computed first
– Place the action of a synthesized attribute for the head of a production at the
end of the body of the production
Implementing L-Attributed SDD