CS/COE 447: Spring 2010 Programming Assignment 2 YOUR NAME: Your solution to this assignment must be your own individual work. Programming assignments should be uploaded to coursed at http://courseweb.pitt.edu. Create a zip file with your three programs, and call it "PA2" followed by your name. The individual files should be called, e.g., "PA2part1" followed by your name. This programming assignment focuses on using the stack. The stack is used for various things. For subroutines, it is used to store local variables, passed arguments (when $a0-$a3 are not sufficient), returned values (when $v0 and $v1 are not sufficient), and for return addresses (for subroutines that call other subroutines). A stack is a "Last in First out" (LIFO) data structure, which means that the last thing pushed onto the stack will be the first one removed. In MIPS, the convention is to grow the stack from the highest address to the lowest one, decrementing the stack pointer as you go. The stack pointer $sp (register 29) contains the address of the next item to be removed from the stack, or, in other words, of the element that is currently on top of the stack. In Mars, $sp is initialized to 0x7fffeffc. The stack is empty when $sp = 0x7fffeffc. To push the value in $t0 onto the stack: addi $sp,$sp,-4 sw $t0,0($sp) To pop the top of the stack into $t0: lw $t0,0($sp) addi $sp,$sp,4 ********Part 1 [40 points]: In Part 1, you will write a printf function, _myprintf, inspired by the printf function in C. First, let's see how printf works. If you access the Unix/Linux "man" entries for the printf, you see the following. int printf(const char *format, ...); printf takes a format string and a variable number of arguments (that's what the ... means). It's easy to understand how it works with an example: fn = "John"; mi = "Q"; age = 19; printf("My first name is %s, my middle initial is %c., and I am %d years old.",fn,mn,age) %s is a placeholder for a string, %c is a placeholder for a single character, and %d is a placeholder for an integer. Printf prints out the format string, filling in the variable values for the placeholders. So, this example prints: My first name is John, my middle initial is Q., and I am 19 years old. The interesting thing for our purposes is that printf can be called with any number of different types of arguments. Thus, only the string variable can be passed in a register; we must use the stack for the others. Your task is to write subroutine _myprintf and test it (a second test will be _reverse calling it - see Part 2). To test it, first set up in memory the format string and any other strings you will print, e.g.: .data name: .asciiz "John" city: .asciiz "Pittsburgh" fmt: .asciiz "My first name is %s, my middle initial is %c., I'm %d years old, and I live in %s." Then push appropriate values on the stack (no need for input - just use fixed values), and call _myprintf. #******* #Subroutine myprintf # _myprintf(char *fmt, ...) prints its arguments under control of "fmt". #fmt is a pointer to a character string. The string #is composed of zero or more directives: ordinary characters, which #are simply copied to the output stream, and conversion #specifications, each of which shall result in the fetching and #printing of a single argument. There are three possible conversion #specifications: %s for a string (in which case the argument is a #pointer to a string stored in memory); %d for an integer to be #printed in decimal (in which case the argument is a 1-word integer); #and %c for a char (in which case the argument is a 1-byte character). #Note that syscall has all these options. # #Arguments: $a0 holds ptr to the format string; the rest of the # arguments are on the stack. # # destroys: none (so all registers _myprintf uses should # be saved upon entry and restored before returning) # # returns: none # # You may assume that the format string and pushed arguments are # correct (i.e., that the right types of things are on the stack in # the correct order). # ********Part 2 [25 points] Write and call a procedure _reverse that uses the stack to reverse the order of the elements of a given array and then prints out the reversed values. To do this, push all elements of the array onto the stack, so when you pop them they will be in reverse order. As you pop the elements from the stack, store them in their new proper places in memory. Your array should be stored as a data structure where the first word is the number of elements in the array, followed by the elements of the array. Each element is a 1-word integer. For example: .word array: 25,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 If your procedure is called with "array" as the parameter, after it returns, 25 (the last element) should be stored where the 1 is now (the zeroith element), 24 should be stored where the 2 is now, and so on. The first argument to the procedure is a pointer to the array data structure, in $a0. The second argument, in $a1, is a pointer to space in memory for a format string. There is no result returned. Your procedure should not destroy the values of any of the registers it uses, so it should save their values upon entry and restore them upon exit. Also, your procedure must call your _myprintf subroutine (from Part 1) to print the array after the values have been reversed. To do this, you will need to create the format string, storing it in memory starting at the address stored in $a1. You may assume that sufficient space has been allocated for your format string. Since your procedure is not a leaf procedure, you will need to save the return address on the stack. To test your code, set up the array and space for the format string in memory, then call _reverse. ********Part 3 [35 points]: For Part 3, translate the following recursive function into assembly. Also, write code to call your function with some sample values. Please follow the calling conventions for registers: the arguments should be passed in the \$a registers and the result should be returned in \$v0. Also, see the "Preserved Across a Call?" column on the green card. int foo (int n, int f0, int f1) { if(n == 0) return f0; else if (n == 1) return f1; else return 2 * foo(n-1,f0,f1) + foo(n-2,f0,f1)/2; } To be certain that the result does not overflow \$v0, assume that your function will be called with f0 and f1 between 0 and 10 and n between 1 and 25. Also, the division should be integer division by 2. You can do this by performing a shift. In general, a "function frame" or an "activation record" of a subroutine is a data structure in the stack that contains, if any, saved argument registers, saved return address, saved "saved" registers, and variables that are local to the subroutine. For function foo, your activation record will need to contain saved registers and the saved return address. To help you test your code, here are the returned values for n from 1 to 10, with f0 and f1 both equal to 10: 0 10 1 10 2 25 3 55 4 122 5 271 6 603 7 1341 8 2983 9 6636 10 14763