You are on page 1of 13

CS241 Final Exam Review Solutions

Winter 2018

1 Notes and disclaimer


These review solutions may contain errors and are intended merely as a reference to check your own answers
against. They may also be terse compared to what the question asks for.

2 Context-free Languages
1. Give an example of a language which is context-free but not regular.
Solution:
L = {an bn : n 0}

2. What does it mean for a context-free grammar to be ambiguous? Give an example of an ambiguous
grammar.
Solution: A grammar is ambiguous if some word in the language specified by the grammar has two
parse trees. The following grammar is ambiguous:

S ! SS
S!a

The word aaa can be represented by the following two parse trees:
S S

S S S S

a S S S S a

a a a a

3. Provide both a leftmost and rightmost derivation of aaabbc using the grammar specified by the following
production rules:

S ! aABc
A ! aA
A!✏
B ! bb

1
Solution: Leftmost: S ) aABc ) aaABc ) aaaABc ) aaaBc ) aaabbc
Rightmost: S ) aABc ) aAbbc ) aaAbbc ) aaaAbbc ) aaabbc
4. Design a context-free grammar for the language of strings over ⌃ = {a, b, c} of words of the form
an bn+m cm with n, m > 0.
Solution: Notice that this is just the languages an bn and bm cm concatenated.
S ! AC
A ! aAb
A ! ab
C ! bCc
C ! bc

5. Recall that the WLM rule for if statements looks like this (with some words abbrevited):

stmt ! IF LPAREN test RPAREN LBRACE stmts RBRACE ELSE LBRACE stmts RBRACE

Add grammar rules to support the following extended if statement syntax:

if (condition1) {
// statements
} else if (condition2) {
// more statements
// [more else ifs]
} else { // else is still mandatory
// statements
}

That is, you should support any number (zero or more) “else if”s in a WLM if statement.
Solution: While C does this slightly di↵erently, we can hack in an “elseifs” nonterminal to the IF rule:

stmt ! IF LPAREN test RPAREN LBRACE stmts RBRACE elseifs ELSE LBRACE stmts RBRACE
elseifs ! elseif elseifs
elseifs ! ✏
elseif ! ELSE IF LPAREN test RPAREN LBRACE stmts RBRACE

3 LL Parsing
3.1 Identifying incorrect grammars
Argue why each of the following CFGs are not LL(1) by constructing enough of the predictor table to show
an issue:
1.
S0 ! ` S a (0)
S ! Sab (1)
S ! xy (2)

2
2.

S0 ! ` S a (0)
S ! abc (1)
S ! abd (2)
S ! bSc (3)

Solution:
1. The predictor table will contain both 1 and 2 in the PREDICT(S, x) cell.

2. The predictor table will contain both 1 and 2 in the PREDICT(S, a) cell.

3.2 Parsing correct grammars


Construct a predictor table for the following CFG and use it to parse the string ` deccb a.

S0 ! ` S a (0)
S ! aY (1)
S ! Zb (2)
Z ! XY (3)
X ! de (4)
X!✏ (5)
Y ! ccY (6)
Y !✏ (7)

Solution:

Nonterminal Nullable? Nonterminal First Nonterminal Follow


S’ N S’ {`} S’ ;
S N S {a, b, c, d} S {a}
X Y X {d} X {b, c}
Y Y Y {c} Y {b, a}
Z Y Z {c, d} Z {b}

Predictor table
` a a b c d e
S’ 0
S 1 2 2 2
X 5 5 4
Y 7 7 6
Z 3 3 3

3
Action Consumed Input Stack Remaining Input
initialize S’ ` deccb a
expand rule 0 `Sa ` deccb a
match ` ` Sa deccb a
expand rule 2 ` Zb a deccb a
expand rule 3 ` XY b a deccb a
expand rule 4 ` deY b a deccb a
match d `d eY b a eccb a
match e ` de Yba ccb a
expand rule 6 ` de ccY b a ccb a
match c ` dec cY b a cb a
match c ` decc Yba ba
expand rule 7 ` decc ba ba
match b ` deccb a a
match a ` deccb a
done

4 LR Parsing
4.1 Identifying incorrect grammars
Show each of the following grammars are not LR(0) by constructing enough of the automaton to show an
issue:
1.
S ! aSb
S!a

2.
S ! Xab
S ! Y cd
X!e
Y !e

Solution:
1. The second state of the machine will contain:
S ! a • Sb
S ! a•
S ! •aSb
S ! •a

This constitutes a shift-reduce conflict.


2. There will be a state of the machine containing:

X ! e•
Y ! e•

This constitutes a reduce-reduce conflict.

4
4.2 Parsing correct grammars
Construct a SLR(1) machine for the following CFG and use it to parse the string ` baabbb a.

S0 ! ` X a (0)
X ! XbAb (1)
X!✏ (2)
A ! Aa (3)
A!✏ (4)

Solution:

0 2 a 3
S 0 !` X• a
S0 ! • ` X a S 0 !` X a •
X ! X • bAb

` X b

1 4
S 0 ! ` •X a X ! Xb • Ab
X ! •XbAb A ! •Aa
X ! ✏• {b, a} A ! ✏• {a, b}

6 b 5 a 7
X ! XbAb• {b, a} X ! XbA • b A ! Aa• {a, b}
A!A•a

Action State Stack Symbol Stack Remaining Input


initialize 1 ` baabbb a
reduce 2 12 `X baabbb a
shift b 124 ` Xb aabbb a
reduce 4 1245 ` XbA aabbb a
shift a 12457 ` XbAa abbb a
reduce 3 1245 ` XbA abbb a
shift a 12457 ` XbAa bbb a
reduce 3 1245 ` XbA bbb a
shift b 12456 ` XbAb bb a
reduce 1 12 `X bb a
shift b 124 ` Xb ba
reduce 4 1245 ` XbA ba
shift b 12456 ` XbAb a
reduce 1 12 `X a
shift a 123 `Xa
done

5
5 Errors in WLM
Each of the following WLM programs contains one or more errors. For each error you’re given a hint about
roughly where the error occurs via an underline. For each program, identify:

• Which error would be detected first by the compiler (you may assume that if two errors occur in the
same phase, the one which appears first in the code occurs first).
• Which phase of compilation that error occurs in.
• A brief description of why it is an error.

1. int wain ( int a , int b , int c ) {


a = a + 1;
int d = 3;
return b ;
}

2. int wain ( int a ) {


return a * a ;
}

3. int wain ( int a , int b ) {


if ( a < b ) {
return a ;
}

return a > 3 ? a : b ;
}

4. int wain ( int a , int b ) {


int a = 0;
return a + b ;
}

5. int f ( int a ) {
while ( a < b ) a = a + 1;
return a ;
}

int wain ( int a , int b ) {


return f (a , b ) ;
}

Solution:
1. The first underlined error: WLM functions can have at most two parameters. This is a parse error.

6
2. The only underlined error: wain must have exactly two parameters. This is a context-sensitive error.
3. The third underlined error: ? is not a token in WLM. This is a scanning error.
4. The only underlined error: a is redefined. This is a context-sensitive error.
5. The first underlined error: while loops must have braces in WLM. This is a parse error.

6 Compiling code
Alice is frustrated with the lack of switch statements in WLM and decides to add them to the language,
using the same syntax as C. Which parts of the compiler (scanning, parsing, context-sensitive analysis, and
code generation) need to be changed? For each part that needs to be changed, give a high-level overview of
what needs to be added, removed, or otherwise changed in that stage.
For a reminder of C’s switch statement syntax, Alice might want to write the following code:

switch (3 + 4) {
case 5: foo(); break;
case 7: bar(); break;
default: break;
}

Solution:
• Scanning needs to support the SWITCH, CASE, DEFAULT, BREAK keywords as well as the COLON token.
• Parsing needs rules for the switch statement.
• Context-sensitive analysis does not need to be changed.
• Code generation needs to be able to generate the MIPS code corresponding to switch statements. This
will be fairly similar to code generation for if statements.

7 Code Generation
7.1 Translating WLM programs to MIPS
Give MIPS assembly code which are equivalent to the following WLM code snippets. For full marks, your
code should work even when the statements, expressions, and mathematical operations within the main
statement or expression are replaced by arbitrary other subexpressions, statements, or operations. You may
use the following WLM conventions in addition to the usual MIPS conventions:

• $29 is the frame pointer.


• $1 and $2 contain the arguments to a procedure. Fproc is the label associated with procedure proc.
• ST[a] is the o↵set of a from $29.
• $3 contains the result of any expression or procedure call.
• $4 contains 4, and $11 contains 1.

You may also reuse the code for previous parts by saying, for example, code(part1) to refer to the code
generated by part 1. You may do this even if you didn’t complete the previous part.

1. 4

7
2. 4 + 4

3. ((4 + 4))

4. a

5. a = ((4 + 4));

6. while ( a < 4) {
a = ((4 + 4));
}

7. f ( g () , 4)

Solution: These solutions use the following two short forms:


; push $s
sw $s , -4( $30 )
sub $30 , $30 , $4
; pop $d
add $30 , $30 , $4
lw $d , -4( $30 )

1. lis $3
. word 4

2. code ( part1 )
push $3
code ( part1 )
pop $5
add $3 , $5 , $3

3. code ( part2 )

4. lw $3 , ST [ a ]( $29 )

5. code ( part3 )
sw $3 , ST [ a ]( $29 )

6. while0 : ; Assume unique label each time


code ( part4 )
push $3
code ( part1 )
pop $5
slt $3 , $5 , $3
beq $3 , $0 , endwhile0
code ( part5 )
beq $0 , $0 , while0
endwhile0 : ; Assume unique label each time

8
7. ; Assume $31 is already saved by the procedure
lis $31
. word Fg
jalr $31
push $3
code ( part1 )
pop $1
add $2 , $3 , $0
lis $31
. word Ff
jalr $31

7.2 Generating code for new rules


Bob is maintaining a WLM compiler and his users are frustrated that they have to type “i = i + 1”, “c = c
+ d”, and other cumbersome expressions: they miss the “+=” syntax from C. As a result, he modifies the
scanner so that it can generate a “PLUSEQ” token for +=, and adds the following rule to the grammar:

statement ! ID PLUSEQ expr SEMI


However, he’s forgotten MIPS and is stuck on code generation. Write Scala, C++ or Racket code to
generate the MIPS assembly code corresponding to this rule and print it to standard output. You may
use symbolTable(ID) to refer to the o↵set of the variable from $29, and code(expr) to refer to the code
corresponding to expr.
Solution:
Scala :
val offset = symbolTable ( ID )
println ( code ( expr ))
println ( s " lw $$5 , $offset ( $$29 ) " )
println ( " add $3 , $5 , $3 " )
println ( s " sw $$3 , $offset ( $$29 ) " )

C ++:
code ( expr )
std :: cout << " lw $5 , " << symbolTable ( ID ) << " ( $29 ) " << std :: endl ;
std :: cout << " add $3 , $5 , $3 " << std :: endl ;
std :: cout << " sw $3 , " << symbolTable ( ID ) << " ( $29 ) " << std :: endl ;

Racket :
( code expr )
( displayln " lw $5 , " ( number - > string ( symbolTable ID )) " ( $29 ) " )
( displayln " add $3 , $5 , $3 " )
( displayln " sw $3 , " ( number - > string ( symbolTable ID )) " ( $29 ) " )

8 Memory Management
8.1 Fixed-size memory management
Finish the WLM program below which implements a fixed-size memory allocator for triple. You may
assume some of the functions (specified below) are implemented already, and that WLM has been extended
to support three-parameter functions. You need to implement triple and detriple.

9
// Implement the triple () and detriple () functions below
// using the a11p1 library as well as the helpers defined below .

int init () {
// You may assume this function is defined correctly
// to initialize the arena if it i s n t already initialized ,
// and do nothing otherwise
}

int freelistFirst () {
// You may assume this function is defined correctly .
// and it returns the address of the first element
// in the freelist . The freelist will always be nonempty unless
// no memory is available , in which case this function returns 0.
// All elements in the freelist point to non - overlapping blocks
// of size 12. The word pointed to by an element of the freelist
// is the next element of the freelist .
}

int setFreelistFirst ( int pointer ) {


// You may assume this function is defined correctly
// and it sets the top of the freelist to the pointer parameter .
//
// NOTE : This will replace the current freelist in its entirety !
}

int triple ( int a , int b , int c ) {


// If 12 bytes of memory are available , this function
// should store a , b , and c in that memory and return
// its address . Otherwise it should return 0.
}

int detriple ( int p ) {


// This function should make the memory associated
// with p available for reuse .
}
As an additional exercise, try implementing the other functions.
Solution:
int init () {
// Memory map :
// arena () + 0: - arenaSize
// arena () + 4: top of freelist
// arena () + 8: start of memory which has never been allocated
int arenaPtr = 0;
int arenaSize = 0;

arenaPtr = arena ();


arenaSize = lw ( arenaPtr );
if ( arenaSize > 0) {
sw ( arenaPtr , 0 - arenaSize );
sw ( arenaPtr + 4 , 0);

10
sw ( arenaPtr + 8 , arenaPtr + 12);
} else {
// Nothing to do : initialization has already happened
}

return 0;
}

// In the event that the freelist temporarily runs out of memory ,


// this function fetches more
int getMoreMemory () {
int arenaPtr = 0;
int arenaEnd = 0;
int nextMemory = 0;
int returnedMemory = 0;
arenaPtr = arena ();
arenaEnd = arenaPtr - lw ( arenaPtr );
nextMemory = lw ( arenaPtr + 8);

if ( nextMemory != 0) {
returnedMemory = nextMemory ;
nextMemory = nextMemory + 12;
if ( nextMemory >= arenaEnd ) {
nextMemory = 0;
} else {}

sw ( returnedMemory , 0);
sw ( arenaPtr + 8 , nextMemory );
} else {}

return returnedMemory ;
}

// Gets the top of the freelist , possibly adding to it if it ’s empty


int freelistFirst () {
int top = 0;
top = lw ( arena () + 4);

if ( top == 0) {
top = getMoreMemory ();
setFreelistFirst ( top );
} else {}

return top ;
}

int setFreelistFirst ( int p ) {


sw ( arena () + 4 , p );
return 0;
}

11
int triple ( int a , int b , int c ) {
int newMem = 0;
newMem = freelistFirst ();

if ( newMem != 0) {
setFreelistFirst ( lw ( newMem ));

sw ( newMem , a );
sw ( newMem + 4 , b );
sw ( newMem + 8 , c );
} else {}

return newMem ;
}

int detriple ( int pair ) {


sw ( pair , freelistFirst ());
setFreelistFirst ( pair );
return 0;
}

8.2 Variable-sized memory management


In both cases, the unspecified implementation of malloc should be O(1) for sparse arenas, and free should
always be O(1).

1. Suppose a user requests 4 bytes of memory. Give a memory diagram for a block of memory, including
the size of each component, that might be returned by malloc. Indicate where the pointer returned by
malloc points to within this block with an arrow.
2. Suppose a user frees the block of memory allocated in the previous part. Assuming the block is not
merged with any adjacent blocks, give a memory diagram for the free block of memory, including the
size of each component.
3. Explain how your block design in the previous parts allows free blocks to be merged with adjacent
blocks when necessary in O(1) total time.
Solution:
Block Size Free? User memory Padding Block size
1. Size 4 4 4 4 4
Contents 20 N [empty] 20
The pointer points to the “User memory” cell.
Block Size Free? Next Prev Block size
2. Size 4 4 4 4 4
Contents 20 Y [old freelist first] [this is the freelist head] 20
3. The trailing block size in a cell can be used to find the start of the cell. From there, the Free? field can
be used to determine if merging with the previous or next block is possible without having to search
the freelist. Since the list is doubly linked, new nodes can easily be merged in in O(1). If merging is
not possibly, simply put the node on the front of the freelist.

12
8.3 Di↵erent fit strategies
Suppose we use a much simpler block layout that in the previous question (which may not support efficient
frees), where each block has at least eight bytes in it and free blocks simply have their size in bytes followed
by a pointer to the next block, while allocated blocks simply have their size in bytes followed by the user’s
memory. Show that best fit is neither always better or worse than first fit by giving (and explaining via a
sequence of arena diagrams), assuming that the head of the freelist is always the most recently deallocated
node:

1. A sequence of allocations and deallocations which can be satisfied by best fit but not first fit.
2. A sequence of allocations and deallocations which can be satisfied by first fit but not best fit.

Use a 256-byte arena in both cases.


Solution: In both cases, the allocations represent the total amount of memory allocated, including any
padding needed for the allocation algorithm.

1. a = alloc (64)
b = alloc (64)
c = alloc (64)
d = alloc (64)
free ( a )
e = alloc (32)
free ( d )
f = alloc (32)
g = alloc (64)

2. a = alloc (64)
b = alloc (64)
c = alloc (64)
d = alloc (64)
free ( a )
e = alloc (32)
free ( d )
f = alloc (32)
free ( b )
g = alloc (96)

13

You might also like