CS143 Final Exam Solutions (2006 exam) (1) (a) abcABC123 321CBAcba BaB1cAb AAAAacac1231 (b) S → Sa | a | (c) I0: S’ → • S, $ S → • , $/( S → • [ S ] S, $/( S → • S ( S ), $/( On [, shift/goto I1 I1: S → [ • S ] S, $/( S → • , ]/( S → • [ S ] S, ]/( S → •

S ( S ), ]/( Reduce on the epsilon production, goto I2 I2: S → [ S • ] S, $/( S → S • ( S ), ]/( On ], shift/goto I3 I3: S → [ S ] • S, $/( S → • , $/( S → • [ S ] S, $/( S → • S ( S ), $/( Reduce on the epsilon production, goto I4 I4: S → [ S ] S •, $/( S → S • ( S ), $/( * Shift/reduce conflict on (. Choose shift, goto I5 I5: S → S ( • S ), $/( S → • , )/( S → • [ S ] S, )/( S → • S ( S ), )/( Reduce on epsilon production, goto I6. I6: S → S ( S • ), $/( S → S • ( S ), )/( On ), shift/goto I7. 271 2221 6431 71

1

I7: S → S ( S ) •, $/( Reduce, leaving I3 on top of stack, goto I4 Then reduce (no conflict on $ in I4), leaving I0 on stack, goto the I8. I8: S’ → S •, $ S → S • ( S ), $/( ACCEPT (2) (a) The scanner would need to recognize the two new keywords foreach and in. (b) Stmt → Foreach Foreach → foreach ( ID in ID ) Stmt (c) (Note that we need to use an explicit statement block to get the declaration of the looping variable to work right)
{ int A_indexer; for (A_indexer = 0; A_indexer < A.length(); A_indexer = A_indexer+1) { i = A[i_indexer]; <statement> } }

(d) (NB: There were a number of problems with this question when it occurred on this exam - we won’t ask something like this. Here, however, is one possible answer.)
class ForEachLoop %{ protected: Expr *initExpr; Expr *testExpr; Expr *incrementExpr; Expr *index; Expr *elementAccess; Stmt *newBody; public: ForEachLoop (Identifier *item, Identifier *array, Stmt *origBody); VarDecl *GetIndexVarDecl(); Stmt *GetForStmt(); %} %token T_In T_Foreach %% Stmt: T_Foreach ’(’ T_Identifier T_In T_Identifier ’)’ Stmt { ForEachLoop *loop = new ForeachLoop ($3, $5, $7); List<VarDecl *> decls = new List<VarDecl *>;

2

List<Stmt *> stmts = new List<Stmt *>; decls->Append(loop->GetIndexVarDecl()); stmts->Append(loop->GetForStmt()); $$ = new StmtBlock(decls, stmts); }

(3)

(a) The scanner must recognize the instanceof keyword. (b) The grammar just needs a new expression for the new kind of condition now possible: Expr → ID instanceof ID (c) A grammar line (corresponding to the above) needs to be added. It would also need an action that constructs an AST node corresponding to the new syntax (e.g., $$ = new InstanceOfExpr(...); ). (d) The semantic analyzer must now typecheck binary expressions where ‘instanceof’ is the operator. There is some room for interpretation here, but taking the question the most literally: it needs to check that type of the left-hand expression is some named type (i.e. isn’t a basic type like int), and that the right-hand side is an identifier corresponding to a declared class name. (e) An object o can potentially be an instanceof some class C only if the compile-time type of o is compatible with C. At compile time, therefore, the semantic analyzer can just check this compatibility relationship. If the type of o is not compatible with C, then it would issue the error “o cannot possibly be a C”. (f) This can be done with the existing runtime environment. To check whether some variable x is an instance of some class C at runtime, we just need to check if the vtable (i.e. zero-offset) entry in the block of object information for x points to the vtable for C. This is just a simple comparison - between the value stored in *x (treating x as a pointer) and the address of the vtable for C. (g) The intermediate code generator just needs a standard process for generating TAC (or some other IR) for any instanceof expression. (See below.) (h) We assume that the TAC generator already emits code for vtables according to our standard approach - i.e. in this example, somewhere we already have the TAC: VTable A = LabelForMethod1, LabelForMethod2, ... LabelForMethodN ; For this function itself, we would emit something like: _isA: BeginFunc 8; _t0 = *a; // get the address of a’s vtab _t1 = _t0 == A; // compare to the address of the A vtab Return t1;

3

CS143 Final Exam Solutions (2005 exam) (1) Here is the complete parser.y file - we only graded on the substantive details of the class declarations, %union, precedence and grammar productions.
%{ int yylex(); void yyerror (char *s); class Expression { public: virtual ˜Expression() { } virtual void PostfixPrint() = 0; protected: Expression() { } }; class IntegerConstant : public Expression { private: int value; public: IntegerConstant (int n) : value(n) { } void PostfixPrint() { printf ("%d ", value); } }; class BinaryExpression : public Expression { private: Expression *e1; Expression *e2; char op; public: BinaryExpression(Expression *left, Expression *right, char oper) : e1(left), e2(right), op(oper) { } void PostfixPrint() { e1->PostfixPrint(); e2->PostfixPrint(); printf ("%c ", op); } }; %} %union { int intVal; Expression *expr; } %token <intVal> T_Int; %type <expr> Expr; %left ’+’ ’-’ %right ’*’ ’/’

4

%% Cmd: Expr: Expr: Expr: Expr: Expr: %% void yyerror (char *s) { fprintf (stderr, "%s\n", s); } int main() { yy_flex_debug = false; yydebug = false; yyparse(); } Expr ’\n’ { $1->PostfixPrint(); printf ("\n");}

T_Int { $$ = new IntegerConstant($1); } Expr ’+’ Expr { $$ = new BinaryExpression($1, Expr ’-’ Expr { $$ = new BinaryExpression($1, Expr ’*’ Expr { $$ = new BinaryExpression($1, Expr ’/’ Expr { $$ = new BinaryExpression($1,

$3, $3, $3, $3,

’+’); ’-’); ’*’); ’/’);

} } } }

(2)

(3) (See Section 4 handout) (4) (See Section 5 handout)

5

(5)

(a) The general idea here certainly makes sense. Right now, the whole struct is stored as (using one character for byte, with ‘o’ meaning unusued) as: axxxbbbbccxx A more compact ordering would be (b, c, a), yielding: bbbbccax ... with only one byte of padding (since the whole struct still needs to be word aligned). The problem is that the C programmer may manipulate memory directly - perhaps even using the padding areas for some purpose - and therefore depend on the fields being in the order specified - e.g.:
void foo(struct binky obj, short tag, int value) { *((char *)obj + 1) = 1; // set a flag in byte #1 *((short *)obj + 5) = tag; // write tag into last two bytes *((short *)obj + 4) = (short) (value >> 16); // copy upper two bytes of value into c }

None of this code will work the way the programmer intended if we “helpfully” reorganize memory for them. (b) The reason that ACall has a two-load overhead is that we need to traverse the runtime structures in memory when a method is called: from the this pointer of the object at hand, to the vtable that object points to, to a particular entry in that vtable. In the general case, this is necessary because the run-time type of the object affects which actual method is dispatched. The main situation in which we can avoid all this is if there is no question about which method will be dispatched at runtime. Suppose we have the method call obj.M(...); where obj’s compile-time class is C. We can optimize in this way if: ** There is no declaration for M given in any of C’s descendants, OR ** C is already a leaf of the inheritance tree (no classes inherit from it), making the first condition degeneratively true. If this holds, the ACall (along with the preliminary pointer arithmetic) can be replaced with a direct LCall to the correct method. During semantic analysis, for any method call we just need to recursively search the classes that are descendants of C. If no declaration for M is found in them, we can implement the optimization. At code-generation, the LCall to generate is to C.M if M is declared in class C, or to Cprime.M, where that latter label corresponds to Cprime::M() such that Cprime is the nearest ancestor of C that includes a declaration of M.

6

Master your semester with Scribd & The New York Times

Special offer for students: Only $4.99/month.

Master your semester with Scribd & The New York Times

Cancel anytime.