In this lab we will write and test a
program that will traverse a string stored in the .data segment
using the .asciiz
directive, and count the total number of decimal digits “0” to “9”
within the string. It will also compute a sum of all the digits it
encounters. To do this, write a main routine that traverses the
characters of the string one by one and for each character calls a
subroutine called is_a_digit.
If the passed character is not a digit, is_a_digit
returns -1, otherwise it returns the actual value of the digit
(i.e. 0 through 9). If the returned value is not -1, the main
routine should increment a count value and it should also add
the digit to an accumulating sum. These values will ultimately
be saved in .word variables called "dCount" and "dSum"
respectively (just before the program ends in an infinite
loop). Also note that "Count" is the name of a co-processor
register, using it as a variable name will confuse the
debugger.
Calling Convention
Note that you do not need
to create stack frames
for this lab. The topic of stack
frames will be introduced shortly in class if it hasn't been
already. However, you will
have to follow the convention about which registers are
preserved and not preserved across a call. That is, we must
assume that the is_a_digit
subroutine can freely modify any call-scratch registers
(a0 -
a3, v0, v1, and t0 - t9) but must
preserve and restore the value of any other registers that it
uses (on the stack). Be careful to use s registers
for values that need to be preserved across any subroutine
calls. These are often referred to as "live" values.
As discussed in class, the
result of the calling convention is that any call-preserved
registers (s0 - s7,
ra, fp, gp) that are used in a routine (main
or any subroutines it calls) need to be saved on the stack at
the beginning of the routine and restored at the end of the
routine. Also, any call-scratch registers that hold live
values and need to be preserved across a call need to be saved
just before the call and restored before that value is needed
again (usually just after the call returns). Because calls are
often inside loops, there will be more saving and restoring of
call-scratch
(t)
registers than there would be if call-preserved (s)registers
were used for live values. Since our programs will not need
many live values, I will
deduct points for using t registers for live values. However,
it is perfectly fine (and in fact preferable) to use t (and other
call-scratch) registers for any values that are not live. Also
note that sp
is also a call preserved register, but its value is preserved
by making sure we release the same amount of space at the end
of a routine as we allocated at the beginning. I will check to
make sure the sp
is returned (at the end of main) to the same value it had at
the beginning of main.
Remember that the calling
convention allows us to write functions so that they could be
used by someone else who understands the calling convention
and what is passed and returned (i.e, the functions could be
placed in a library). Don't be tempted to say "I know that my
is_a_digit
routine doesn't use t9, so I can use t9 for a live value
in my main routine".
As you work with more functions in a program it
becomes a nightmare to keep track of which functions use
which registers. Further, if the is_a_digit
routine was a library routine, you would not have any way of
knowing what registers it used. Also note that the is_a_digit
routine will be used in a future lab.
Function is_a_digit:
As noted above, this function is
used to determine if an ASCII character passed to it represents a
decimal digit and returns its value if it is. If the character is
not a digit, the function returns -1 (0xffffffff).
Determining if a character is a digit can be done by range
checking. The ASCII code for the digits “0” to “9” are the
hex values 0x30 to 0x39. Pseudo-code for is_a_digit is shown
below.