2005Sp CS61C Project 2: sprintf in MIPS

Due 23:59:59 on 2005-03-04

TA In Charge: Danny

Administrative Requirements

To submit your project, create a directory named proj2 that contains your sprintf.s file. From within that directory, type "submit proj2".


This document mentions certain test cases. However, this input/output behavior is not a comprehensive specification of all possible behavior. Therefore, even if your project submission passes all of the test cases mentioned here, it may not be entirely correct. We strongly urge you to create test cases in addition to the ones provided here.


The MIPS procedure calling convention uses registers $a0-$a3 for passing arguments down to procedures. If there are more than four, the remaining arguments are passed on the stack. Each argument gets one word of stack space. Suppose we are trying to write in MIPS assembler a program like this:

int foo (int x, int y, int quux, int bar, int baz) {

     int a, b;
     a = y;

int main () {
     int c, d;
     foo (3, 4, 43, 62, 1);
Procedure foo has five integer arguments. The space for those arguments is allocated on the stack as part of the caller's stack frame. In other words, main , not foo , must allocate the space. The arguments go at the bottom of main's stack frame. That is, main will use 0($sp) to hold the argument x and 4($sp) to hold the argument y. The first argument is always at the top of the stack. you have to be consistent about this so that foo knows which argument is which. N.B.: Stack space is allocated for all arguments, even those which are passed in the $a* registers. Those values are not put on the stack, but stack space is nonetheless reserved for them.
        addi $t0, $0, 3     # assume this holds the value of c
        addi $t1, $0, 4     # assume this holds the value of d
        addi $sp, $sp, -32  # make stack space for 8 words: 
                            # $ra, c, d, and 
                            # the args x, y, quux, bar, baz
        sw   $ra, 28($sp)   # save $ra
        sw   $t0, 24($sp)   # save c before calling foo
        sw   $t1, 20($sp)   # save d before calling foo
        add  $a0, $0, $t0   # arg x = c
        add  $a1, $0, $t1   # arg y = d
        addi $a2, $0, 43    # arg quux = 43
        addi $a3, $0, 62    # bar = 62
        addi $t0, $0, 1     # baz = 1, but no more registers
        sw   $t0, 16($sp)   # so pass on the stack
        jal  foo
        addi $sp, $sp, 32   # restore stack space
        lw   $ra, -4($sp)   # reload return address
        jr   $ra            # return to caller

foo:    addi $sp, $sp, -12  # make stack space for 3 words:
                            # $ra, a, b
        sw   $ra, 8($sp)    # save $ra
        add  $t0, $0, $a1   # get argument y
        lw   $t1, 28($sp)   # *** (see below)
                            # 12 (foo's frame) + 16 = 28 up on stack
                            # fetched argument baz
        addi $sp, $sp, 12   # restore stack space
        lw   $ra, -4($sp)   # reload return address
        jr   $ra            # return to caller

The instruction indicated by "***" is the key to understanding the stack method of argument passing. Procedure foo is referring to a word of stack memory that is from the caller's stack frame. Its own frame includes only the three words 0($sp) , 4($sp) , and 8($sp).


Write a MIPS assembly language implementation of a function very similar to the C function sprintf:

int sprintf (char *outbuf, char *format, ...)

sprintf works like printf, except that it writes to the string outbuf instead of to standard output. outbuf is assumed already to point to allocated memory sufficient to hold the generated characters--see man sprintf for more information. Your function must accept any number of arguments, passed according to MIPS standard conventions: Stack space is allocated for all the arguments, but the first four arguments are passed in registers anyway; their stack space is unused. The first four arguments are passed in the $a0-$a3 registers, and the rest are passed on the stack.

The first argument is the address of a character array into which your procedure will put its results. The second argument is the address 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. The remaining arguments are values that are to be converted to printable character form according to the format instructions. sprintf returns the number of characters in its output string not including the null at the end.

You do not have to do any error checking (e.g. comparing the number of arguments to the number of % specifications). You also do not have to implement all of the formatting options of the real sprintf. Here are the ones you are to implement:

Standard sprintf options:

New sprintf options:

Donít implement width or precision modifiers (e.g., %6d ). Copy the files from ~cs61c/w/hw/proj2/lib into your project directory. (The directory is also available on the web, here: lib). There are three files supplied: First, sprintf.s, a template file for implementing sprintf. Next, spf-main.s, a main program to call sprintf (with a few included test cases). Lastly, there's test.pl, which is a perl script which you can use to run your test-cases through spim. Additionally, there's a subdirectory called tests, which contains four test cases you can use. We encourage you to make additional test cases using the same form as spf-main.s. You can use test.pl as follows to help you with your test cases: Running it as is will test against spf-main.s, or, if you give it any other file (or a list of files), it will run that file (or those files) with your sprintf.s code: (run from the ~cs61c/w/hw/proj2/lib directory)

unix$ perl test.pl
97 characters:
string: thirty-nine, unsigned dec: 255, hex: 0xff, char: o, dec: -255, bin: 11111111, percent: %
unix$ perl test.pl tests/*
443 characters:
%F: FLOATS: 0: -0.0, NaN: NaN, infinity: -infinity, bigneg: -11111111111111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0, smallneg: -0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000, negative four: -100.000000000000000000000, negative half: -0.100000000000000000000000
59 characters:
%NUM: ARBITRARY BASE: 2: 1100100, 3: 10201, 4: 1210 5: 400
71 characters:
string: thirty-nine, unsigned dec: 255, hex: 0xff, char: o, dec: -255.
59 characters:
binary: 0b11111111, percent: %, octal: 0377, decimal: 255.

Only your sprintf.s will be graded. You should create additional test cases to test your code more thoroughly.

Miscellaneous Requirements


You can assume that you will always have a correctly formatted format string, which contains only the control characters listed above. You can assume that you will always have enough arguments for control characters.