05-MIPS-examples

05-MIPS-examples - MIPS examples We’ve learned all of the...

Info iconThis preview shows page 1. Sign up to view the full content.

View Full Document Right Arrow Icon
This is the end of the preview. Sign up to access the rest of the document.

Unformatted text preview: MIPS examples We’ve learned all of the important features of the MIPS instruction set architecture, so now it’s time for some examples! — First we’ll see a nested function, which calls another function. — Next up is a demonstration of recursion. — Finally we’ll work with some C-style strings. This week’s sections will discuss how structures and objects can be stored and manipulated in MIPS. You’ll also get some debugging practice, which will be useful for the assignment that will appear later today. February 5, 2003 ©2001-2003 Howard Huang 1 Combinations Writing a function to compute combinations based on the fact code from last time will illustrate the implementation of nested functions. A mathematical definition of combinations is: n k n! = k! (n—k)! The corresponding C or Java code is shown below. int comb(int n, int k) { return fact(n) / fact(k) / fact(n-k); } Looks easy, right? February 5, 2003 MIPS examples 2 Look before you leap! int comb(int n, int k) { return fact(n) / fact(k) / fact(n-k); } We use MIPS registers to represent the argument and return values. — Arguments n and k are passed in caller-saved registers $a0 and $a1. — The return value (an integer) is placed in $v0. The arguments n and k are used multiple times—in particular, after the function calls fact(n) and fact(k). We’ll have to save the original values of $a0 and $a1, to ensure they aren’t overwritten by fact. comb is a nested function, which means it also acts as a callee. We must also preserve callee-saved registers such as $ra properly. The return expression involves two divisions and requires one temporary register in MIPS. We’ll use $s0 for illustrative purposes. February 5, 2003 MIPS examples 3 Callee-saved registers To summarize, comb will need to preserve and restore four registers: $ra $s0 $a0 $a1 comb is the callee in relation to whoever calls it (e.g., main). — Thus, comb is responsible for preserving the callee-saved registers $ra and $s0, which it will modify. — It’s easiest to save them at the beginning of the function, and to restore them right before returning. February 5, 2003 MIPS examples comb: sub sw sw $sp, $sp, 8 $ra, 0($sp) $s0, 4($sp) ... main body ... lw lw addi jr $ra, 0($sp) $s0, 4($sp) $sp, $sp, 8 $ra 4 Caller-saved registers However, comb is the caller in relation to the fact function. — This means comb must store the callersaved registers $a0 and $a1 before it calls fact, and restore them afterwards. — We’ll allocate two additional words on the stack for $a0 and $a1. They’ll be restored as necessary in the function. Is it possible to preserve $a0 and $a1 in any of the other registers, instead of the stack? February 5, 2003 MIPS examples comb: sub sw sw sw sw $sp, $ra, $s0, $a0, $a1, $sp, 16 0($sp) 4($sp) 8($sp) 12($sp) ... main body ... lw lw addi jr $ra, 0($sp) $s0, 4($sp) $sp, $sp, 16 $ra 5 The rest of comb The call fact(n) is easy. — The value n is also the first argument of comb, so it is already in $a0. — The result is saved in register $s0. # fact(n) jal fact move $s0, $v0 For fact(k): — We have to load k from memory, in case fact(n) overwrote $a1. — $s0 is updated with fact(n) / fact(k). # fact(k) lw $a0, 12($sp) jal fact div $s0, $s0, $v0 For fact(n-k): — We have to restore both n and k. — The final result of comb ends up in $s0. # fact(n-k) lw $a0, 8($sp) lw $a1, 12($sp) sub $a0, $a0, $a1 jal fact div $s0, $s0, $v0 February 5, 2003 MIPS examples 6 The whole kit and caboodle The whole function is shown on the right. Don’t forget to return the result! We must move $s0 to $v0 before restoring registers and returning. That’s a lot of work for a simple one-line C function. Compilers are good, mmkay? February 5, 2003 MIPS examples comb: sub sw sw sw sw jal move lw jal div lw lw sub jal div move lw lw addi jr $sp, $ra, $s0, $a0, $a1, fact $s0, $a0, fact $s0, $a0, $a1, $a0, fact $s0, $v0, $ra, $s0, $sp, $ra $sp, 16 0($sp) 4($sp) 8($sp) 12($sp) $v0 12($sp) $s0, $v0 8($sp) 12($sp) $a0, $a1 $s0, $v0 $s0 0($sp) 4($sp) $sp, 16 7 Famous Fibonacci Function The Fibonacci sequence is often expressed recursively: fib(n) = 0 1 fib(n-1) + fib(n-2) if n = 0 if n=1 otherwise This is easy to convert into a C program. int fib(int n) { if (n <= 1) return n; else return fib(n-1) + fib(n-2); } The translation to MIPS is not bad if you understood the first example. February 5, 2003 MIPS examples 8 Some observations about fib This function is similar to comb in some ways. — fib calls another function (itself), so we will have to save $ra. — We need to save the argument n across the first recursive call. — We need a temporary register for the result of the first call. A recursive function also acts as both caller and callee. — Calling the same function guarantees that the same registers will be used, and overwritten if we’re not careful. — So to be careful, we have to save every register that is used. February 5, 2003 MIPS examples 9 The base case The base case of the recursion is easy. If $a0 is less than 1, then we just return it. (We won’t worry about testing for valid inputs here.) This part of the code does not involve any function calls, so there’s no need to preserve any registers. int fib(int n) { if (n <= 1) return n; else return fib(n-1) + fib(n-2); } fib: bgt $a0, 1,recurse move $v0, $a0 jr $ra February 5, 2003 MIPS examples 10 Doing the recursion First save $ra and the argument $a0. An extra word is allocated on the stack to save the result of fib(n-1). The argument n is already in $a0, so we can decrement it and then “jal fib” to implement the fib(n-1) call. The result is put into the stack. recurse: sub $sp, $sp, 12 sw $ra, 0($sp) sw $a0, 4($sp) addi $a0, $a0, -1 jal fib sw $v0, 8($sp) Retrieve n, and then call fib(n-2). lw $a0, 4($sp) addi $a0, $a0, -2 jal fib The results are summed and put in $v0. lw add We only need to restore $ra before popping our frame and saying bye-bye. February 5, 2003 MIPS examples $v1, 8($sp) $v0, $v0, $v1 lw $ra, 0($sp) addi $sp, $sp, 12 jr $ra 11 The complete fib fib: bgt $a0, 1, recurse move $v0, $a0 jr $ra recurse: sub $sp, $sp, 12 sw $ra, 0($sp) sw $a0, 4($sp) addi jal sw lw addi jal lw add $a0, fib $v0, $a0, $a0, fib $v1, $v0, $a0, -1 8($sp) 4($sp) $a0, -2 8($sp) $v0, $v1 lw $ra, 0($sp) addi $sp, $sp, 12 jr $ra February 5, 2003 MIPS examples 12 Representing strings A C-style string is represented by an array of bytes. — Elements are one-byte ASCII codes for each character. — A 0 value marks the end of the array. 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 space ! ” # $ % & ’ ( ) * + , . / February 5, 2003 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 0 1 2 3 4 5 6 7 8 9 : ; < = > ? 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 @ A B C D E F G H I J K L M N O 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 P Q R S T U V W X Y Z [ \ ^ _ MIPS examples 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 ` a b c d e f g h I j k l m n o 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 p q r s t u v w x y z { | } ~ del 13 String manipulation For example, “Harry Potter” can be stored as a 13-byte array. 72 97 H a 114 114 121 r r y 32 80 P 111 116 116 101 114 o t t e r 0 \0 We can convert a string to uppercase by manipulating the ASCII values. — Lowercase letters a-z have ASCII codes from 97 to 122. — Uppercase letters A-Z range from 65 to 90. — A lowercase letter can be converted to uppercase by subtracting 32 from the ASCII code (e.g., 97 – 32 = 65). February 5, 2003 MIPS examples 14 Two versions of ToUpper Both of these loop through a string, subtracting 32 from lowercase letters, until they reach the terminating 0. The first one accesses letters by indexing the array str, and incrementing the index on each loop iteration. The second one uses a pointer that produces a letter when it’s dereferenced. The pointer is incremented by one on every loop iteration. February 5, 2003 void ToUpper(char str) { int i = 0; while (str[i] != 0) { if (str[i] >= 97 && str[i] <= 122) str[i] = str[i] - 32; i++; } } void ToUpper(char *str) { char *s = str; while (*s != 0) { if (*s >= 97 && *s <= 122) *s = *s - 32; s++; } } MIPS examples 15 Array version A direct translation of the array version means each iteration of the loop must re-compute the address of str[i]—requiring two additions. toupper: li $t0, loop: add $t1, lb $t2, beq $t2, blt $t2, bgt $t2, sub $t2, sb $t2, next: addi $t0, j loop exit: jr $ra 0 # $t0 = i $t0, $a0 0($t1) $0, exit 97, next 122, next $t2, 32 0($t1) # # # # # # # $t0, 1 # i++ $t1 = &str[i] $t2 = str[i] $t2 = 0? $t2 < 97? $t2 > 122? convert and store back Here we work with bytes, but if the array contained integers, this address computation would require a multiplication as well. February 5, 2003 MIPS examples 16 Pointer version Instead, we could use a register to hold the exact address of the current element. Each time through the loop, we’ll increment this register to point to the next element. toupper: lb $t2, 0($a0) beq $t2, $0, exit blt $t2, 97, next bgt $t2, 122, next sub $t2, $t2, 32 sb $t2, 0($a0) next: addi $a0, $a0, 1 j toupper exit: jr $ra # # # # # # $t2 = *s $t2 = 0? $t2 < 97? $t2 > 122? convert and store back # s++ With an array of words, we would have to increment the pointer by 4 on each iteration. But then we would only need one addition, instead of two additions and a multiplication. February 5, 2003 MIPS examples 17 Side by side toupper: li $t0, loop: add $t1, lb $t2, beq $t2, blt $t2, bgt $t2, sub $t2, sb $t2, next: addi $t0, j loop exit: jr $ra toupper: 0 $t0, $a0 0($t1) $0, exit 97, next 122, next $t2, 32 0($t1) lb beq blt bgt sub sb next: addi j exit: jr $t0, 1 $t2, 0($a0) $t2, $0, exit $t2, 97, next $t2, 122, next $t2 $t2, 32 $t2, 0($a0) $a0, $a0, 1 toupper $ra Another similar example is in Section 3.11 of Hennessy and Patterson. February 5, 2003 MIPS examples 18 Summary These three examples demonstrate that writing large, modular programs in assembly language is difficult! — It’s hard to figure out how to best use the limited number of registers. (Register allocation is an important problem in writing compilers.) — We must always follow the MIPS function call conventions regarding the passing and returning of values and preservation of registers. — There is only one addressing mode that must be used for all memory accesses, whether to arguments or stack-allocated space. You’ll get some good programming practice on the machine problem. February 5, 2003 MIPS examples 19 ...
View Full Document

Ask a homework question - tutors are online