CSCE 531
Spring 2019
Project Part II
Due Thursday April 11, 2019

Process C function definitions and C expression statements.

As with Part I, scores are for graduate students; undergraduates get a 10% boost.

To receive 80% of the credit: You must be able to handle function definitions. This includes determining the function return type (you are only responsible for handling functions that return int, char, float, and double), installing the function in the symbol table, generating the necessary assembly code to enter and exit the function, and generating the assembly code for the function body (the statements inside the function). At this level you do not need to handle parameter or local variable declarations.

For this assignment the only legal statement inside a function is an `expression_statement'. For the 80% level of this assignment, expression statements can only be made up of: the assignment operator (=), the basic arithmetic binary operators (+,-,*,/), the unary minus arithmetic operator (-), unsigned integer constants, unsigned floating-point constants, the basic comparison operators (==,<,>,!=,<=,>=), global variables (only types int, char, float, or double), parentheses, and function calls to functions that have no parameters. Please note: the result type of an arithmetic operator (+,-,*,/) is the same as the common type of its (binary converted) operands, but the result type of a comparison operator (==,<,>,!=,<=,>=) is always int, regardless of the type of the operands. The type of an assignment operator is the same as the type of its left-hand side, and its value is the same as the value assigned to its left-hand side (that is, after the right-hand side is assignment-converted; see the discussion of conversions below).

To receive full credit at the 80% level, you should also perform "constant folding" as the expression trees are constructed. Any operator that is detected as having constant operands should be evaluated at compile time (the subtree rooted at the operator is replaced by a constant tree node containing the value of the result).

You are responsible for doing the appropriate C type checking of these expression statements and determining the necessary type conversions. Be sure you understand the C type conversion rules. Implement the "traditional" C rules for type conversions. See the section "C Conversions" below.

For compatibility with legacy code, C (but not C99) allows a function call involving an undeclared function name (it is presumably in another file, or appears later in the current file), and just assumes the function will return a value of type int. To simplify matters, we will adopt the C99 convention and not allow this: every function name appearing in a function call must already be declared or defined (in the same compilation unit). However, you should assume that a function definition without a return type specification defines a function that returns int.

You are also responsible for doing the appropriate semantic error checking. For example, the use of an undeclared identifier in an expression is illegal, even if it is the function name in a function call.

You need not issue an error message for an expression statement that has no side effects. That is, this statement may be processed without a message:

                       x+6;

Above, the value of the expression "x+6" is simply ignored. Since we are not yet implementing the return statement, you do not need to check that the proper type is being returned by a function being defined. To "ignore" the value of an non-void expression, you simply pop its value off of the stack (call b_pop()). For expressions of void type (i.e., calls to functions with void return type), there is nothing to do; no value will be pushed in the first place (and calling b_pop() in this case may corrupt the control stack at run time, leading to undefined behavior).

To receive 90% of the credit: In addition to obtaining the 80% level, you must add parameter declarations, parameter references in expressions, and function calls that include parameter lists, including reference parameters. You are not responsible for `old style' definitions of parameters, for example,

int f(a, b, c)
char *a;
int c;
double b;
{ ... }

You are not responsible for detecting parameter number or parameter type mismatches between the definition of a function and a call to that function. You only need to handle parameters of type int, char, float, double, and pointer types (also allow string literals to be passed as parameters). Be sure you understand what it means for a parameter to be of type char or float (see "C Conversions," below or Harbison and Steele, Section 9.4).

You will not be calling any functions that have reference parameters (so all arguments to a function call are r-values), but you need to support reference parameters in functions that you define.

To receive 100% credit: In addition to obtaining the 90% level, you must fully implement compound statements and you must add the increment (++) and decrement(--) operators to your repertoire of arithmetic operators. Implementing compound statements means you must support local variable declarations and local variable references within expression statements. Since compound statements can be nested, this means you must be able to implement C's block structured scope rules. You only need to handle local variable declarations of type int, char, float, and double. Note that in the version of C we are implementing, all variable declarations in a block must come before any statements in that block, that is, statements and declarations cannot be mixed. (This is enforced by the grammar.)

At all levels you are responsible for detecting the relevant duplicate declarations.

The appropriate back end routines to generate x86 assembly code for this assignment are discussed in class and described in the comments in backend-x86.h.

You may assume there will not be any initializers in the source code. You are also not responsible for processing storage class specifiers on any declaration.

As in Project Part I: your compiler should be capable of detecting multiple semantic errors in one file, and you may allow the compiler to stop processing with the first syntax error.

C Conversions

There are four kinds of conversions in C: these are called the usual conversions (Harbinson & Steele, C: A Reference Manual (5th ed.), Section 6.3):

We will use the conversion rules for Traditional C rather than Standard C. The rules for the various usual conversions relevant to the project are as follows (here, T represents any data type):

Always apply the default function argument conversions (i.e., the unary conversions) to formal arguments to a function call, even if the function has a prototype or a previous definition. You are not required to check the number and types of actual arguments against the number and types of the corresponding formal parameters (again, even if the function has a prototype).

When a unary operator is applied in an expression and that operator expects an r-value, the r-value of its operand is unary-converted before the operator is applied. When a binary operator (expecting two r-values) is applied, each operand is first unary converted, then the operands are then binary converted (which affects only one of the two operands at most).

The relevant back end routine for applying a conversion operator is b_convert().

Testing

The file proj2test.zip unzips to a directory containing test ".c" files, as well as library routines that are to be compiled and linked to your object code to form an executable. The directory also contains sample inputs and outputs when the executable is run, along with the testing script proj2-test.pl (used just like proj1-test.pl) and the comments.txt file produced when the script is run on my solution code. These may not be the only test files that will be used when grading, so do your own testing too. Run

    ./pcc3 < T2Lxxx_ok.c > T2Lxxx_ok.s

as you did for Project I, where 'xxx' is the level number. Then run

    gcc -m32 T2Lxxx_ok.s libxxx.c

each time you want to execute your program, or run

    gcc -m32 -c libxxx.c

once, then run

    gcc -m32 T2Lxxx_ok.s libxxx.o

on subsequent tests on the same level. Note that there is no lib100.c, as none is needed. Finally, run

    ./a.out

and type in any input that may be requested. You may want to add to your Makefile to automate this process. (The "-m32" option tells gcc to assume a 32-bit memory model when compiling. The assembly code emitted by your compiler is targeted to a 32-bit architecture.)

It will be helpful to compare your compiler's assembly code output with that of the solution, but the assembly code need not match exactly. There are many legitimate reasons why there may not be an exact match that do not affect the correctness of the code. Here is how we will grade via the script proj2-test.pl:

You will need the 80% level functionality (minus constant folding) in order to do later parts of the project. So be sure you at least get that much of the assignment completed.

Remember: you get credit for features successfully implemented. You do not get credit for attempting to do something; you get credit for the things that you can successfully demonstrate work.

Submission

Also remember: as always you are expected to do your own work on this assignment. Read the section on plagiarism in the syllabus.

Finally: you should adequately document and structure your program. Remember you may be asked to explain this program orally during a subsequent "quiz."

The project is due at 11:59 pm on the date given above. As before, late submissions will be accepted up to one week late (see the syllabus).

You must turn in all source files (even the ones we gave you) and a Makefile for your compiler. Do not turn in any automatically generated files; run `make clean' before submitting. To turn in this assignment, name your directory proj2 and use the departmental dropbox (not Blackboard). As with Project I, upload a single file proj2.tar.gz.


This page was last modified Monday April 15, 2019 at 10:23:19 EDT.