This is an individual assignment; you must work alone. Submit your solution, a file named snprintf.s, as proj2, using the command.
submit proj2
on your instructional UNIX account.
This project should give you more practice with representation of data types, in particular, working with how they are passed as arguments on a MIPS processor. It will also give you more experience with MIPS assembly language programming. And it will really solidify your understanding of calling conventions. snprintf is especially challenging because it takes a variable number of arguments. These are called VARARGS in C.
Make a directory named proj2 in your home directory. Then copy the file ~cs61cl/code/snprintf.s to the directory; this is a main program that includes several calls to snprintf, and into which you should put your code.
Note carefully the line below which all your changes must be made. Do not make any changes above that line. Our autograder will strip off that segment of code and replace it by other test calls.
############################################################################ ################# ALL YOUR CHANGES MUST BE BELOW THIS LINE ################# ############################################################################
You are to provide a MIPS assembly language implementation of the C function snprintf:
int snprintf (char *outbuf, size_t outbufsize, char *format, ...)
snprintf is similar to printf, except that it writes to the first argument, the string outbuf, instead of to standard output (the screen). A terminating null character ('\0') is written to outbuf prior to return from snprintf. The second argument specifies the size of the output buffer referred to by outbuf, in bytes (we will be using ASCII for this project so 1 byte = 1 character). The third argument is the format string, much like printf's.
'\0'
Some special cases:
outbufsize
Your function must accept any number of arguments, passed according to MIPS standard conventions: If there are more than four arguments, the additional ones will be spilled to the stack, with the first "spilled" argument in the lowest stack position. (This means the address closer to zero.)
The first argument is the address of a character array into which your procedure will put its results.
The second argument is the number of characters that the array can hold. This count includes space for the terminating null byte.
The third argument is the address of the start of a format string in which each occurrence of a percent sign (%) indicates where one of the subsequent arguments is to be substituted and how it is to be formatted.
snprintf returns the number of characters it wrote to its output string, not including the null at the end. The return value will not exceed outbufsize-1.
Since there can be an arbitrary number of arguments to the snprintf function, all arguments after the fourth (if they exist) will be passed on the stack. Before your function is called, the caller will save any extra arguments to the stack in the same way $s0-$s7 registers are saved (by doing sw $s0, offset($sp). In order to access these variables, you will need to figure out the correct offset in relation to the callee stack pointer.
For example, if the caller stores an argument in 0($sp), the callee might access the argument at 8($sp), depending on how the stack pointer is moved in the callee function. (NOTE: It is not always an offset of 8; it depends on how your code moves the stack).
If this is at all confusing to you, please read this tutorial on MIPS stack management with more detailed information and nifty diagrams.
Within snprintf.s, you will be implementing format specifiers that differ slightly from those in the ANSI snprintf. The ones you are to implement are listed below. All are case-sensitive.
%d: Interpret the argument word as a signed decimal value, and write the signed decimal representation with no leading zeroes to the output string.
%u: Interpret the argument word as an unsigned value, and write its decimal representation (again with no leading zeroes) to the output string.
%x: Interpret the argument word as an unsigned value, and write its hexadecimal representation (again with no leading zeroes) to the output string (use uppercase A-F).
A-F
%c: Interpret the low-order byte of the argument word as a character, and write it to the output string. (This means that all arguments to snprintf, even single characters, occupy 4 bytes = 1 word.)
%s: Interpret the argument as a pointer to the start of a null-terminated string. Copy this string to the output. Don't change the output if the character pointer is null.
%%: "%%" should output "%", "%%%%" should output "%%" and so on.
Invalid specifiers: If you receive an escape sequence not specified above, output the character preceded by %*. Ex: "foo %Q" should output "foo %*Q". It should not consume arguments.
% as last character of format string: Simply output a %. Ex: "%" should output "%".
%
Variable width or precision modifiers (e.g., %5.2f).
String length limits (e.g., %5f).
%5f
The ANSI standard notes that for various error cases, snprintf's behavior is undefined. Listed below are some error cases, along with how you should handle them for this project.
You may assume that there at least as many things to print as there are format specifiers. It would be nice to be able to test this, but it isn't possible. (Hint: you'll soon see why.)
Obey all register conventions in this project. return the number of characters in $v0 and do not use $s? registers without saving them first. In particular, remember that the $a? registers are temporary registers, so they will be clobbered by any function call. Points will be deducted for violations of these conventions. Your snprintf procedure must work with the main function supplied in snprintf.s Also, bear in mind that our autograder assumes that snprintf obeys register conventions (and will probably test for it!).
You must comment your MIPS code. The graders will read it, and they need to be able to quickly understand what every section does. A common style for commenting assembly is to include a long introductory comment before every block of assembly statements, with a short comment after every line telling what it does. The introductory comment must describe the algorithm that the following block implements. The line-by-line comments must just be an easy-to-read version of the assembly code, using real variable names and perhaps more C-like constructs. Your code, like ours, should be at leasat 50% comments, if not more. Professionally written assembly is often 75% or more comments.
Don't print leading zeroes. For all the number formatting cases, you must minimize the number of digits your snprintf prints. For example, if the user tries to format the number 19 (decimal), your snprintf must format it as "13" in hexadecimal, and not as, say, "00000013".
Use breakpoints to debug your MIPS code. MARS provides fantastic debugging functionality. Use it, or you'll regret it.
Build incrementally. You should build up your MIPS code gradually by implementing one element of your snprintf code one at a time instead of all at once. This will save you time debugging and perhaps you sanity. Thus, a good checkpoint is being able to return the format string. After that, you can then try to handle each format specifier one by one.
You should write a complete set of test cases as part of your solution. These will not be submitted, but building these test cases as you add functionality will allow to quickly verify that any new functionality has not broken your old functionality. Consider automating these tests as much as possible to save yourself time in the long run.
We will post testing tools, but don't rely on us. Do your own testing by modifying the driver code that calls your snprintf. Start simple and add things as you go.