1 of 61

Compiler course

Chapter 5

Syntax Directed Translation

2 of 61

Outline

  • Syntax Directed Definitions
  • Evaluation Orders of SDD’s
  • Applications of Syntax Directed Translation
  • Syntax Directed Translation Schemes

3 of 61

Introduction

  • We can associate information with a language construct by attaching attributes to the grammar symbols.
  • A syntax directed definition specifies the values of attributes by associating semantic rules with the grammar productions.

Production

Semantic Rule

E->E1+T

E.code=E1.code||T.code||’+’

  • We may alternatively insert the semantic actions inside the grammar

E -> E1+T {print ‘+’}

4 of 61

Syntax Directed Definitions

  • A SDD is a context free grammar with attributes and rules
  • Attributes are associated with grammar symbols and rules with productions
  • Attributes may be of many kinds: numbers, types, table references, strings, etc.
  • Synthesized attributes
    • A synthesized attribute at node N is defined only in terms of attribute values of children of N and at N it
  • Inherited attributes
    • An inherited attribute at node N is defined only in terms of attribute values at N’s parent, N itself and N’s siblings

5 of 61

Example of S-attributed SDD

  1. L -> E n
  2. E -> E1 + T
  3. E -> T
  4. T -> T1 * F
  5. T -> F
  6. F -> (E)
  7. F -> digit

Production

Semantic Rules

L.val = E.val

E.val = E1.val + T.val

E.val = T.val

T.val = T1.val * F.val

T.val = F.val

F.val = E.val

F.val = digit.lexval

6 of 61

Example of mixed attributes

  1. T -> FT’

  • T’ -> *FT’1

3) T’ -> ε

  1. F -> digit

Production

Semantic Rules

T’.inh = F.val

T.val = T’.syn

T’1.inh = T’.inh*F.val

T’.syn = T’1.syn

T’.syn = T’.inh

F.val = F.val = digit.lexval

7 of 61

Evaluation orders for SDD’s

  • A dependency graph is used to determine the order of computation of attributes
  • Dependency graph
    • For each parse tree node, the parse tree has a node for each attribute associated with that node
    • If a semantic rule defines the value of synthesized attribute A.b in terms of the value of X.c then the dependency graph has an edge from X.c to A.b
    • If a semantic rule defines the value of inherited attribute B.c in terms of the value of X.a then the dependency graph has an edge from X.c to B.c
  • Example!

8 of 61

Ordering the evaluation of attributes

  • If dependency graph has an edge from M to N then M must be evaluated before the attribute of N
  • Thus the only allowable orders of evaluation are those sequence of nodes N1,N2,…,Nk such that if there is an edge from Ni to Nj then i<j
  • Such an ordering is called a topological sortof a graph
  • Example!

9 of 61

S-Attributed definitions

  • An SDD is S-attributed if every attribute is synthesized
  • We can have a post-order traversal of parse-tree to evaluate attributes in S-attributed definitions

postorder(N) {

for (each child C of N, from the left) postorder(C);

evaluate the attributes associated with node N;

}

  • S-Attributed definitions can be implemented during bottom-up parsing without the need to explicitly create parse trees

10 of 61

L-Attributed definitions

  • A SDD is L-Attributed if the edges in dependency graph goes from Left to Right but not from Right to Left.
  • More precisely, each attribute must be either
    • Synthesized
    • Inherited, but if there us a production A->X1X2…Xn and there is an inherited attribute Xi.a computed by a rule associated with this production, then the rule may only use:
      • Inherited attributes associated with the head A
      • Either inherited or synthesized attributes associated with the occurrences of symbols X1,X2,…,Xi-1 located to the left of Xi
      • Inherited or synthesized attributes associated with this occurrence of Xi itself, but in such a way that there is no cycle in the graph

11 of 61

Application of Syntax Directed Translation

  • Type checking and intermediate code generation (chapter 6)
  • Construction of syntax trees
    • Leaf nodes: Leaf(op,val)
    • Interior node: Node(op,c1,c2,…,ck)
  • Example:
  1. E -> E1 + T
  2. E -> E1 - T
  3. E -> T
  4. T -> (E)
  5. T -> id
  6. T -> num

Production

Semantic Rules

E.node=new node(‘+’, E1.node,T.node)

E.node=new node(‘-’, E1.node,T.node)

E.node = T.node

T.node = E.node

T.node = new Leaf(id,id.entry)

T.node = new Leaf(num,num.val)

12 of 61

Syntax tree for L-attributed definition

+

  1. E -> TE’

  • E’ -> + TE1’

  • E’ -> -TE1’

  • E’ -> ∈

  • T -> (E)

  • T -> id
  • T -> num

Production

Semantic Rules

E.node=E’.syn

E’.inh=T.node

E1’.inh=new node(‘+’, E’.inh,T.node)

E’.syn=E1’.syn

E1’.inh=new node(‘+’, E’.inh,T.node)

E’.syn=E1’.syn

E’.syn = E’.inh

T.node = E.node

T.node=new Leaf(id,id.entry)

T.node = new Leaf(num,num.val)

13 of 61

Syntax directed translation schemes

  • An SDT is a Context Free grammar with program fragments embedded within production bodies
  • Those program fragments are called semantic actions
  • They can appear at any position within production body
  • Any SDT can be implemented by first building a parse tree and then performing the actions in a left-to-right depth first order
  • Typically SDT’s are implemented during parsing without building a parse tree

14 of 61

Postfix translation schemes

  • Simplest SDDs are those that we can parse the grammar bottom-up and the SDD is s-attributed
  • For such cases we can construct SDT where each action is placed at the end of the production and is executed along with the reduction of the body to the head of that production
  • SDT’s with all actions at the right ends of the production bodies are called postfix SDT’s

15 of 61

Example of postfix SDT

  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;}

16 of 61

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

17 of 61

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

18 of 61

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 appears on the top of the parser stack
    • If the parser is top down we perform “a” just before we expand Y
  • Sometimes we cant do things as easily as explained above
  • One example is when we are parsing this SDT with a bottom-up parser
  1. L -> E n
  2. E -> {print(‘+’);} E1 + T
  3. E -> T
  4. T -> {print(‘*’);} T1 * F
  5. T -> F
  6. F -> (E)
  7. F -> digit {print(digit.lexval);}

19 of 61

SDT’s with actions inside productions (cont)

  • Any SDT can be implemented as follows
    1. Ignore the actions and produce a parse tree
    2. Examine each interior node N and add actions as new children at the correct position
    3. Perform a postorder traversal and execute actions when their nodes are visited

L

E

+

E

{print(‘+’);}

T

F

digit

{print(4);}

T

T

F

*

digit

{print(5);}

F

digit

{print(3);}

{print(‘*’);}

20 of 61

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 dpendent 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

21 of 61

Example

S -> while (C) S1 L1=new();

L2=new();

S1.next=L1;

C.false=S.next;

C.true=L2;

S.code=label||L1||C.code||label||L2||S1.code

S -> while ( {L1=new();L2=new();C.false=S.next;C.true=L2;}

C) {S1.next=L1;}

S1{S.code=label||L1||C.code||label||L2||S1.code;}

22 of 61

Outline

  • Variants of Syntax Trees
  • Three-address code
  • Types and declarations
  • Translation of expressions
  • Type checking
  • Control flow
  • Backpatching

23 of 61

Introduction

  • 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

Parser

Static

Checker

Intermediate Code Generator

Code Generator

Front end

Back end

24 of 61

Variants of syntax trees

  • 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

+

+

*

*

-

b

c

a

d

25 of 61

SDD for creating DAG’s

  1. E -> E1+T
  2. E -> E1-T
  3. E -> T
  4. T -> (E)
  5. T -> id
  6. T -> num

Production

Semantic Rules

E.node= new Node(‘+’, E1.node,T.node)

E.node= new Node(‘-’, E1.node,T.node)

E.node = T.node

T.node = E.node

T.node = new Leaf(id, id.entry)

T.node = new Leaf(num, num.val)

Example:

  1. p1=Leaf(id, entry-a)
  2. P2=Leaf(id, entry-a)=p1
  3. p3=Leaf(id, entry-b)
  4. p4=Leaf(id, entry-c)
  5. p5=Node(‘-’,p3,p4)
  6. p6=Node(‘*’,p1,p5)
  7. p7=Node(‘+’,p1,p6)
  1. p8=Leaf(id,entry-b)=p3
  2. p9=Leaf(id,entry-c)=p4
  3. p10=Node(‘-’,p3,p4)=p5
  4. p11=Leaf(id,entry-d)
  5. p12=Node(‘*’,p5,p11)
  6. p13=Node(‘+’,p7,p12)

26 of 61

Value-number method for constructing DAG’s

  • Algorithm
    • Search the array for a node M with label op, left child l and right child r
    • If there is such a node, return the value number M
    • If not create in the array a new node N with label op, left child l, and right child r and return its value
  • We may use a hash table

=

+

10

i

id

To entry for i

num

10

+

1

2

3

1

3

27 of 61

Three address code

  • In a three address code there is at most one operator at the right side of an instruction
  • Example:

+

+

*

*

-

b

c

a

d

t1 = b – c

t2 = a * t1

t3 = a + t2

t4 = t1 * d

t5 = t3 + t4

28 of 61

Forms of three address instructions

  • x = y op z
  • x = op y
  • x = y
  • goto L
  • if x goto L and ifFalse x goto L
  • if x relop y goto L
  • Procedure calls using:
    • param x
    • call p,n
    • y = call p,n
  • x = y[i] and x[i] = y
  • x = &y and x = *y and *x =y

29 of 61

Example

  • do i = i+1; while (a[i] < v);

L: t1 = i + 1

i = t1

t2 = i * 8

t3 = a[t2]

if t3 < v goto L

Symbolic labels

100: t1 = i + 1

101: i = t1

102: t2 = i * 8

103: t3 = a[t2]

104: if t3 < v goto 100

Position numbers

30 of 61

Data structures for three address codes

  • Quadruples
    • Has four fields: op, arg1, arg2 and result
  • Triples
    • Temporaries are not used and instead references to instructions are made
  • Indirect triples
    • In addition to triples we use a list of pointers to triples

31 of 61

Example

  • b * minus c + b * minus c

t1 = minus c

t2 = b * t1

t3 = minus c

t4 = b * t3

t5 = t2 + t4

a = t5

Three address code

minus

*

minus

c

t3

*

+

=

c

t1

b

t2

t1

b

t4

t3

t2

t5

t4

t5

a

arg1

result

arg2

op

Quadruples

minus

*

minus

c

*

+

=

c

b

(0)

b

(2)

(1)

(3)

a

arg1

arg2

op

Triples

(4)

0

1

2

3

4

5

minus

*

minus

c

*

+

=

c

b

(0)

b

(2)

(1)

(3)

a

arg1

arg2

op

Indirect Triples

(4)

0

1

2

3

4

5

(0)

(1)

(2)

(3)

(4)

(5)

op

35

36

37

38

39

40

32 of 61

Type Expressions

Example: int[2][3]

array(2,array(3,integer))

  • A basic type is a type expression
  • A type name is a type expression
  • A type expression can be formed by applying the array type constructor to a number and a type expression.
  • A record is a data structure with named field
  • A type expression can be formed by using the type constructor for function types
  • If s and t are type expressions, then their Cartesian product s*t is a type expression
  • Type expressions may contain variables whose values are type expressions

33 of 61

Type Equivalence

  • They are the same basic type.
  • They are formed by applying the same constructor to structurally equivalent types.
  • One is a type name that denotes the other.

34 of 61

Declarations

35 of 61

Storage Layout for Local Names

  • Computing types and their widths

36 of 61

Storage Layout for Local Names

  • Syntax-directed translation of array types

37 of 61

Sequences of Declarations

  • Actions at the end:

38 of 61

Fields in Records and Classes

39 of 61

Translation of Expressions and Statements

  • We discussed how to find the types and offset of variables
  • We have therefore necessary preparations to discuss about translation to intermediate code
  • We also discuss the type checking

40 of 61

Three-address code for expressions

41 of 61

Incremental Translation

42 of 61

Addressing Array Elements

  • Layouts for a two-dimensional array:

43 of 61

Semantic actions for array reference

44 of 61

Translation of Array References

Nonterminal L has three synthesized attributes:

  • L.addr
  • L.array
  • L.type

45 of 61

Conversions between primitive types in Java

46 of 61

Introducing type conversions into expression evaluation

47 of 61

Abstract syntax tree for the function definition

fun length(x) =

if null(x) then 0 else length(tl(x)+1)

This is a polymorphic function

in ML language

48 of 61

Inferring a type for the function length

49 of 61

Algorithm for Unification

50 of 61

Unification algorithm

boolean unify (Node m, Node n) {

s = find(m); t = find(n);

if ( s = t ) return true;

else if ( nodes s and t represent the same basic type ) return true;

else if (s is an op-node with children s1 and s2 and

t is an op-node with children t1 and t2) {

union(s , t) ;

return unify(s1, t1) and unify(s2, t2);

}

else if s or t represents a variable {

union(s, t) ;

return true;

}

else return false;

}

51 of 61

Control Flow

boolean expressions are often used to:

  • Alter the flow of control.
  • Compute logical values.

52 of 61

Short-Circuit Code

53 of 61

Flow-of-Control Statements

54 of 61

Syntax-directed definition

55 of 61

Generating three-address code for booleans

56 of 61

translation of a simple if-statement

57 of 61

Backpatching

  • Previous codes for Boolean expressions insert symbolic labels for jumps
  • It therefore needs a separate pass to set them to appropriate addresses
  • We can use a technique named backpatching to avoid this
  • We assume we save instructions into an array and labels will be indices in the array
  • For nonterminal B we use two attributes B.truelist and B.falselist together with following functions:
    • makelist(i): create a new list containing only I, an index into the array of instructions
    • Merge(p1,p2): concatenates the lists pointed by p1 and p2 and returns a pointer to the concatenated list
    • Backpatch(p,i): inserts i as the target label for each of the instruction on the list pointed to by p

58 of 61

Backpatching for Boolean Expressions

59 of 61

Backpatching for Boolean Expressions

  • Annotated parse tree for x < 100 || x > 200 && x ! = y

60 of 61

Flow-of-Control Statements

61 of 61

Translation of a switch-statement