MIPS Assembly Tutorial by Daniel J Ellard
MIPS Assembly Tutorial by Daniel J Ellard
MIPS Assembly Tutorial by Daniel J Ellard
Daniel J. Ellard
September, 1994
Contents
1 Data Representation 1
1.1 Representing Integers . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1.1 Unsigned Binary Numbers . . . . . . . . . . . . . . . . . . . . 1
1.1.1.1 Conversion of Binary to Decimal . . . . . . . . . . . 2
1.1.1.2 Conversion of Decimal to Binary . . . . . . . . . . . 4
1.1.1.3 Addition of Unsigned Binary Numbers . . . . . . . . 4
1.1.2 Signed Binary Numbers . . . . . . . . . . . . . . . . . . . . . 6
1.1.2.1 Addition and Subtraction of Signed Binary Numbers 8
1.1.2.2 Shifting Signed Binary Numbers . . . . . . . . . . . 9
1.1.2.3 Hexadecimal Notation . . . . . . . . . . . . . . . . . 9
1.2 Representing Characters . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.3 Representing Programs . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.4 Memory Organization . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.4.1 Units of Memory . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.4.1.1 Historical Perspective . . . . . . . . . . . . . . . . . 13
1.4.2 Addresses and Pointers . . . . . . . . . . . . . . . . . . . . . . 13
1.4.3 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.5 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.5.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.5.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.5.3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2 MIPS Tutorial 17
2.1 What is Assembly Language? . . . . . . . . . . . . . . . . . . . . . . 17
2.2 Getting Started: add.asm . . . . . . . . . . . . . . . . . . . . . . . . 18
2.2.1 Commenting . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.2.2 Finding the Right Instructions . . . . . . . . . . . . . . . . . . 19
i
ii CONTENTS
Data Representation
by Daniel J. Ellard
1
2 CHAPTER 1. DATA REPRESENTATION
Binary Decimal
0 = 0
1 = 1
10 = 2
11 = 3
100 = 4
101 = 5
110 = 6
.. .. ..
. . .
11111111 = 255
• Let i be a counter.
1. Let D = 0.
2. Let i = 0.
Binary Decimal
00000000 = 0 = 0 = 0
00000101 = 22 + 20 = 4+1 = 5
00000110 = 22 + 21 = 4+2 = 6
00101101 = 25 + 23 + 22 + 20 = 32 + 8 + 4 + 1 = 45
length n are used, starting from zero, the largest number will be 2n − 1.
• Let i be a counter.
2. Let i = (n − 1).
3. While i ≥ 0 do:
(a) If D ≥ 2i , then
• Set Xi = 1 (i.e. set bit i of X to 1).
• Set D = (D − 2i ).
(b) Set i = (i − 1).
• Let i be a counter.
• Let s be an integer.
1. Let c = 0.
2. Let i = 0.
(a) Set s = Ai + Bi + c.
(b) Set Xi and ĉ according to the following rules:
• If s == 0, then Xi =0 and ĉ = 0.
• If s == 1, then Xi =1 and ĉ = 0.
• If s == 2, then Xi =0 and ĉ = 1.
• If s == 3, then Xi =1 and ĉ = 1.
(c) Set c = ĉ.
(d) Set i = (i + 1).
6 CHAPTER 1. DATA REPRESENTATION
2. Let X = x̄ + 1.
If this addition overflows, then the overflow bit is discarded.
Figure 1.3 shows the process of negating several numbers. Note that the negation
of zero is zero.
00000110 = 6
1’s complement 11111001
Add 1 11111010 = -6
11111010 = -6
1’s complement 00000101
Add 1 00000110 = 6
00000000 = 0
1’s complement 11111111
Add 1 00000000 = 0
• The leftmost (most significant) bit also serves as a sign bit; if 1, then the number
is negative, if 0, then the number is positive or zero.
• The largest positive number that can be represented in two’s complement no-
tation in an n-bit binary number is 2n−1 − 1. For example, if n = 8, then the
largest positive number is 01111111 = 27 − 1 = 127.
00000101 = 5
+ 11110101 = -11
11111010 = -6
Subtraction is also done in a similar way: to subtract A from B, take the two’s
complement of A and then add this number to B.
The conditions for detecting overflow are different for signed and unsigned num-
bers, however. If we use algorithm 1.3 to add two unsigned numbers, then if c is
1 when the addition terminates, this indicates that the result has an absolute value
too large to fit the number of bits allowed. With signed numbers, however, c is not
relevant, and an overflow occurs when the signs of both numbers being added are the
same but the sign of the result is opposite. If the two numbers being added have
opposite signs, however, then an overflow cannot occur.
For example, consider the sum of 1 and −1:
00000001 = 1
+ 11111111 = -1
00000000 = 0 Correct!
In this case, the addition will overflow, but it is not an error, since the result that
we get (without considering the overflow) is exactly correct.
On the other hand, if we compute the sum of 127 and 1, then a serious error
occurs:
01111111 = 127
+ 00000001 = 1
10000000 = -128 Uh-oh!
Therefore, we must be very careful when doing signed binary arithmetic that we
take steps to detect bogus results. In general:
• If A and B are of the same sign, but A + B is of the opposite sign, then an
overflow or wraparound error has occurred.
1.1. REPRESENTING INTEGERS 9
00000001 = 1
Double 00000010 = 2
Halve 00000000 = 0
00110011 = 51
Double 01100110 = 102
Halve 00011001 = 25
11001101 = -51
Double 10011010 = -102
Halve 11100110 = -26
or Japanese). Already new character sets which address these problems (and can be
used to represent characters of many languages side by side) are being proposed, and
eventually there will unquestionably be a shift away from ASCII to a new multilan-
guage standard1 .
The first six bits (reading from the left, or high-order bits) of each instruction
are called the op field. The op field determines whether the instruction is a regis-
ter, immediate, or jump instruction, and how the rest of the instruction should be
interpreted. Depending on what the op is, parts of the rest of the instruction may
represent the names of registers, constant memory addresses, 16-bit integers, or other
additional qualifiers for the op.
If the op field is 0, then the instruction is a register instruction, which generally
perform an arithmetic or logical operations. The funct field specifies the operation
to perform, while the reg1 and reg2 represent the registers to use as operands, and
the des field represents the register in which to store the result. For example, the
32-bit hexadecimal number 0x02918020 represents, in the MIPS instruction set, the
operation of adding the contents of registers 20 and 17 and placing the result in
register 16.
Field op reg1 reg2 des shift funct
Width 6 bits 5 bits 5 bits 5 bits 5 bits 6 bits
Values 0 20 17 16 0 add
Binary 000000 10100 10001 10000 00000 100000
If the op field is not 0, then the instruction may be either an immediate or jump
instruction, depending on the value of the op field.
nipulated in discrete groups, and these groups are said to be the memory of the
computer.
piece of memory– the two terms are synonymous, although there are many contexts
where one is commonly used and the other is not.
The memory of the computer itself can often be thought of as a large array (or
group of arrays) of bytes of memory. In this model, the address of each byte of
memory is simply the index of the memory array location where that byte is stored.
1.4.3 Summary
In this chapter, we’ve seen how computers represent integers using groups of bits, and
how basic arithmetic and other operations can be performed using this representation.
We’ve also seen how the integers or groups of bits can be used to represent sev-
eral different kinds of data, including written characters (using the ASCII character
codes), instructions for the computer to execute, and addresses or pointers, which
can be used to reference other data.
There are also many other ways that information can be represented using groups
of bits, including representations for rational numbers (usually by a representation
called floating point), irrational numbers, graphics, arbitrary character sets, and so
on. These topics, unfortunately, are beyond the scope of this book.
1.5. EXERCISES 15
1.5 Exercises
1.5.1
Complete the following table:
Decimal 123
Binary 01101100
Octal 143
Hex 3D
ASCII Z
1.5.2
1. Invent an algorithm for multiplying two unsigned binary numbers. You may
find it easiest to start by thinking about multiplication of decimal numbers
(there are other ways as well, but you should start on familiar ground).
1.5.3
1. Invent an algorithm for dividing two unsigned binary numbers. You may find
it easiest to start by thinking about long division of decimal numbers.
2. Your TF complains that the division algorithm you invented to solve the pre-
vious part of this problem is too slow. She would prefer an algorithm that gets
an answer that is “reasonably close” to the right answer, but which may take
considerably less time to compute. Invent an algorithm that has this prop-
erty. Find the relationship between “reasonably close” and the speed of your
algorithm.
16 CHAPTER 1. DATA REPRESENTATION
Chapter 2
MIPS Tutorial
by Daniel J. Ellard
This section is a quick tutorial for MIPS assembly language programming and the
SPIM environment1 . This chapter covers the basics of MIPS assembly language, in-
cluding arithmetic operations, simple I/O, conditionals, loops, and accessing memory.
17
18 CHAPTER 2. MIPS TUTORIAL
chine and assembly languages, each different machine architecture usually has its own
assembly language (in fact, each architecture may have several), and each is unique 2 .
The advantage of programming in assember (rather than machine language) is
that assembly language is much easier for a human to read and understand. For
example, the MIPS machine language instruction for adding the contents of registers
20 and 17 and placing the result in register 16 is the integer 0x02918020. This
representation is fairly impenetrable; given this instruction, it is not at all obvious
what it does– and even after you figure that out, it is not obvious, how to change the
result register to be register 12.
In the meanwhile, however, the MIPS assembly instruction for the same operation
is:
This is much more readable– without knowing anything whatsoever about MIPS
assembly language, from the add it seems likely that addition is somehow involved,
and the operands of the addition are somehow related to the numbers 16, 20, and
17. A scan through the tables in the next chapter of this book confirms that add
performs addition, and that the first operand is the register in which to put the sum
of the registers indicated by the second and third operands. At this point, it is clear
how to change the result register to 12!
2.2.1 Commenting
Before we start to write the executable statements of program, however, we’ll need
to write a comment that describes what the program is supposed to do. In the MIPS
assembly language, any text between a pound sign (#) and the subsequent newline
2
For many years, considerable effort was spent trying to develop a portable assembly which
could generate machine language for a wide variety of architectures. Eventually, these efforts were
abandoned as hopeless.
2.2. GETTING STARTED: ADD.ASM 19
# end of add.asm
Even though this program doesn’t actually do anything yet, at least anyone read-
ing our program will know what this program is supposed to do, and who to blame
if it doesn’t work3 . We are not finished commenting this program, but we’ve done
all that we can do until we know a little more about how the program will actually
work.
1. A register that will be used to store the result of the addition. For our program,
this will be $t0.
is an essential part of the commenting) we select $t1, and make note of this in
the comments.
3. A register which holds the second number, or a 32-bit constant. In this case,
since 2 is a constant that fits in 32 bits, we can just use 2 as the third operand
of add.
We now know how we can add the numbers, but we have to figure out how to get
1 into register $t1. To do this, we can use the li (load immediate value) instruction,
which loads a 32-bit constant into a register. Therefore, we arrive at the following
sequence of instructions:
# end of add.asm
first item on a line. A location in memory may have more than one label. Therefore, to
tell SPIM that it should assign the label main to the first instruction of our program,
we could write the following:
# Daniel J. Ellard -- 02/21/94
# add.asm-- A program that computes the sum of 1 and 2,
# leaving the result in register $t0.
# Registers used:
# t0 - used to hold the result.
# t1 - used to hold the constant 1.
# end of add.asm
When a label appears alone on a line, it refers to the following memory location.
Therefore, we could also write this with the label main on its own line. This is
often much better style, since it allows the use of long, descriptive labels without
disrupting the indentation of the program. It also leaves plenty of space on the line
for the programmer to write a comment describing what the label is used for, which
is very important since even relatively short assembly language programs may have
a large number of labels.
Note that the SPIM assembler does not permit the names of instructions to be used
as labels. Therefore, a label named add is not allowed, since there is an instruction of
the same name. (Of course, since the instruction names are all very short and fairly
general, they don’t make very descriptive label names anyway.)
Giving the main label its own line (and its own comment) results in the following
program:
# Daniel J. Ellard -- 02/21/94
# add.asm-- A program that computes the sum of 1 and 2,
# leaving the result in register $t0.
# Registers used:
# t0 - used to hold the result.
# t1 - used to hold the constant 1.
# end of add.asm
22 CHAPTER 2. MIPS TUTORIAL
2.2.3.2 Syscalls
The end of a program is defined in a very different way. Similar to C, where the exit
function can be called in order to halt the execution of a program, one way to halt a
MIPS program is with something analogous to calling exit in C. Unlike C, however,
if you forget to “call exit” your program will not gracefully exit when it reaches the
end of the main function. Instead, it will blunder on through memory, interpreting
whatever it finds as instructions to execute4 . Generally speaking, this means that
if you are lucky, your program will crash immediately; if you are unlucky, it will do
something random and then crash.
The way to tell SPIM that it should stop executing your program, and also to do
a number of other useful things, is with a special instruction called a syscall. The
syscall instruction suspends the execution of your program and transfers control to
the operating system. The operating system then looks at the contents of register
$v0 to determine what it is that your program is asking it to do.
Note that SPIM syscalls are not real syscalls; they don’t actually transfer control to
the UNIX operating system. Instead, they transfer control to a very simple simulated
operating system that is part of the SPIM program.
In this case, what we want is for the operating system to do whatever is necessary
to exit our program. Looking in table 4.6.1, we see that this is done by placing a 10
(the number for the exit syscall) into $v0 before executing the syscall instruction.
We can use the li instruction again in order to do this:
# Daniel J. Ellard -- 02/21/94
# add.asm-- A program that computes the sum of 1 and 2,
# leaving the result in register $t0.
# Registers used:
# t0 - used to hold the result.
# t1 - used to hold the constant 1.
# v0 - syscall parameter.
# end of add.asm
% spim
SPIM Version 5.4 of Jan. 17, 1994
Copyright 1990-1994 by James R. Larus ([email protected]).
All Rights Reserved.
See the file README a full copyright notice.
Loaded: /home/usr6/cs51/de51/SPIM/lib/trap.handler
(spim)
Whenever you see the (spim) prompt, you know that SPIM is ready to execute
a command. In this case, since we want to run the program that we just wrote, the
first thing we need to do is load the file containing the program. This is done with
the load command:
The load command reads and assembles a file containing MIPS assembly lan-
guage, and then loads it into the SPIM memory. If there are any errors during the
assembly, error messages with line number are displayed. You should not try to ex-
ecute a file that has not loaded successfully– SPIM will let you run the program, but
it is unlikely that it will actually work.
Once the program is loaded, you can use the run command to execute it:
(spim) run
The program runs, and then SPIM indicates that it is ready to execute another
command. Since our program is supposed to leave its result in register $t0, we can
verify that the program is working by asking SPIM to print out the contents of $t0,
using the print command, to see if it contains the result we expect:
5
The exact text will be different on different computers.
24 CHAPTER 2. MIPS TUTORIAL
The print command displays the register number followed by its contents in both
hexadecimal and decimal notation. Note that SPIM automatically translates from
the symbolic name for the register (in this case, $t0) to the actual register number
(in this case, $8).
Once again, we start by writing a comment. From what we’ve learned from
writing add.asm, we actually know a lot about what we need to do; the rest we’ll
only comment for now:
main:
## Get first number from user, put into $t0.
## Get second number from user, put into $t1.
# end of add2.asm.
main:
## Get first number from user, put into $t0.
li $v0, 5 # load syscall read_int into $v0.
syscall # make the syscall.
move $t0, $v0 # move the number read into $t0.
# end of add2.asm.
To put something in the data segment, all we need to do is to put a .data before
we define it. Everything between a .data directive and the next .text directive (or
the end of the file) is put into the data segment. Note that by default, the assembler
starts in the text segment, which is why our earlier programs worked properly even
though we didn’t explicitly mention which segment to use. In general, however, it is
a good idea to include segment directives in your code, and we will do so from this
point on.
We also need to know how to allocate space for and define a null-terminated string.
In the MIPS assembler, this can be done with the .asciiz (ASCII, zero terminated
string) directive. For a string that is not null-terminated, the .ascii directive can
be used (see 4.5.2 for more information).
Therefore, the following program will fulfill our requirements:
.text
main:
la $a0, hello_msg # load the addr of hello_msg into $a0.
li $v0, 4 # 4 is the print_string syscall.
syscall # do the syscall.
# end hello.asm
Note that data in the data segment is assembled into adjacent locations. There-
fore, there are many ways that we could have declared the string "Hello World\n"
and gotten the same exact output. For example we could have written our string as:
.data
hello_msg: .ascii "Hello" # The word "Hello"
.ascii " " # the space.
28 CHAPTER 2. MIPS TUTORIAL
You can use the .data and .text directives to organize the code and data in
your programs in whatever is most stylistically appropriate. The example programs
generally have the all of the .data items defined at the end of the program, but this
is not necessary. For example, the following code will assemble to exactly the same
program as our original hello.asm:
two numbers. Therefore, we’ll start by copying add2.asm, but replacing the add
instruction with a placeholder comment:
.text
main:
## Get first number from user, put into $t0.
li $v0, 5 # load syscall read_int into $v0.
syscall # make the syscall.
move $t0, $v0 # move the number read into $t0.
# end of larger.asm.
Browsing through the instruction set again, we find in section 4.4.3.1 a description
of the MIPS branching instructions. These allow the programmer to specify that
execution should branch (or jump) to a location other than the next instruction. These
instructions allow conditional execution to be implemented in assembler language
(although in not nearly as clean a manner as higher-level languages provide).
30 CHAPTER 2. MIPS TUTORIAL
One of the branching instructions is bgt. The bgt instruction takes three argu-
ments. The first two are numbers, and the last is a label. If the first number is larger
than the second, then execution should continue at the label, otherwise it continues
at the next instruction. The b instruction, on the other hand, simply branches to the
given label.
These two instructions will allow us to do what we want. For example, we could
replace the placeholder comment with the following:
If $t0 is larger, then execution will branch to the t0_bigger label, where $t0 will
be copied to $t2. If it is not, then the next instructions, which copy $t1 into $t2
and then branch to the endif label, will be executed.
This gives us the following program:
.text
main:
## Get first number from user, put into $t0.
li $v0, 5 # load syscall read_int into $v0.
syscall # make the syscall.
move $t0, $v0 # move the number read into $t0.
# end of larger.asm.
.text
main:
## read A into $t0, B into $t1 (omitted).
32 CHAPTER 2. MIPS TUTORIAL
4. Set multiple m = A.
5. Loop:
(a) Print m.
(b) If m == S, then go to the next step.
(c) Otherwise, set m = m + A, and then repeat the loop.
6. Terminate.
2.8. LOADS: THE PALINDROME.ASM PROGRAM 33
loop:
## print out $t3 (omitted)
b loop
endloop:
## exit (omitted)
# end of multiples.asm
1. Let A = S.
(a) Let B = S.
(b) Loop:
• If ∗B == 0 (i.e. the character at address B is 0), then B has gone
past the end of the string. Set B = B − 2 (to move B back past the
0 and the newline), and continue with the next step.
• Otherwise, set B = (B + 1).
3. Loop:
The first step of the algorithm is to read in the string from the user. This can be
done with the read_string syscall (syscall number 8), which is similar in function
to the fgets function in the C standard I/O library. To use this syscall, we need to
load into register $a0 the pointer to the start of the memory that we have set aside
to hold the string. We also need to load into register $a1 the maximum number of
bytes to read.
To set aside the space that we’ll need to need to store the string, the .space
directive can be used. This gives the following code:
.text
main: # SPIM starts by jumping to main.
## read the string S:
la $a0, string_space
li $a1, 1024
li $v0, 8 # load "read_string" code into $v0.
syscall
.data
string_space: .space 1024 # set aside 1024 bytes for the string.
Once we’ve got the string, then we can use algorithm 2.2 (on page 34). The first
step is simple enough; all we need to do is load the address of string_space into
register $t1, the register that we’ve set aside to represent A:
la $t1, string_space # A = S.
The second step is more complicated. In order to compare the character pointed
to by B with 0, we need to load this character into a register. This can be done with
the lb (load byte) instruction:
la $t2, string_space ## we need to move B to the end
length_loop: # of the string:
lb $t3, ($t2) # load the byte at B into $t3.
beqz $t3, end_length_loop # if $t3 == 0, branch out of loop.
addu $t2, $t2, 1 # otherwise, increment B,
b length_loop # and repeat
end_length_loop:
subu $t2, $t2, 2 ## subtract 2 to move B back past
# the ’\0’ and ’\n’.
36 CHAPTER 2. MIPS TUTORIAL
Note that the arithmetic done on the pointer B is done using unsigned arithmetic
(using addu and subu). Since there is no way to know where in memory a pointer
will point, the numerical value of the pointer may well be a “negative” number if it
is treated as a signed binary number .
When this step is finished, A points to the first character of the string and B points
to the last. The next step determines whether or not the string is a palindrome:
test_loop:
bge $t1, $t2, is_palin # if A >= B, it’s a palindrome.
The complete code for this program is listed in section 5.4 (on page 74).
2.9.1 atoi-1
We already know how to read a string, and how to print out a number, so all we need
is an algorithm to convert a string into a number. We’ll start with the algorithm
given in 2.3 (on page 37).
Let’s assume that we can use register $t0 as S, register $t2 as D, and register
$t1 is available as scratch space. The code for this algorithm then is simply:
sum_loop:
lb $t1, ($t0) # load the byte *S into $t1,
addu $t0, $t0, 1 # and increment S.
2.9. THE ATOI PROGRAM 37
Algorithm 2.3 To convert an ASCII string representation of a integer into the cor-
responding integer.
Note that in this algorithm, the operation of getting the character at address X is
written as ∗X.
1. Set D = 0.
2. Loop:
Note that due to a bug in the SPIM assembler, the beq must be given the con-
stant 10 (which is the ASCII code for a newline) rather than the symbolic character
code ’\n’, as you would use in C. The symbol ’\n’ does work properly in strings
declarations (as we saw in the hello.asm program).
A complete program that uses this code is in atoi-1.asm.
2.9.2 atoi-2
Although the algorithm used by atoi-1 seems reasonable, it actually has several
problems. The first problem is that this routine cannot handle negative numbers.
We can fix this easily enough by looking at the very first character in the string, and
doing something special if it is a ’-’. The easiest thing to do is to introduce a new
variable, which we’ll store in register $t3, which represents the sign of the number. If
the number is positive, then $t3 will be 1, and if negative then $t3 will be -1. This
makes it possible to leave the rest of the algorithm intact, and then simply multiply
the result by $t3 in order to get the correct sign on the result at the end:
get_sign:
li $t3, 1
lb $t1, ($t0) # grab the "sign"
bne $t1, ’-’, positive # if not "-", do nothing.
li $t3, -1 # otherwise, set t3 = -1, and
addu $t0, $t0, 1 # skip over the sign.
positive:
sum_loop:
## sum_loop is the same as before.
2.9. THE ATOI PROGRAM 39
end_sum_loop:
mul $t2, $t2, $t3 # set the sign properly.
2.9.3 atoi-3
While the algorithm in atoi-2.asm is better than the one used by atoi-1.asm, it is
by no means free of bugs. The next problem that we must consider is what happens
when S does not point to a proper string of digits, but instead points to a string that
contains erroneous characters.
If we want to mimic the behavior of the UNIX atoi library function, then as
soon as we encounter any character that isn’t a digit (after an optional ’-’) then we
should stop the conversion immediately and return whatever is in D as the result. In
order to implement this, all we need to do is add some extra conditions to test on
every character that gets read in inside sum_loop:
sum_loop:
lb $t1, ($t0) # load the byte *S into $t1,
addu $t0, $t0, 1 # and increment S,
2.9.4 atoi-4
While the algorithm in atoi-3.asm is nearly correct (and is at least as correct as the
one used by the standard atoi function), it still has an important bug. The problem
40 CHAPTER 2. MIPS TUTORIAL
is that algorithm 2.3 (and the modifications we’ve made to it in atoi-2.asm and
atoi-3.asm) is generalized to work with any number. Unfortunately, register $t2,
which we use to represent D, can only represent 32-bit binary number. Although
there’s not much that we can do to prevent this problem, we definitely want to detect
this problem and indicate that an error has occurred.
There are two spots in our routine where an overflow might occur: when we
multiply the contents of register $t2 by 10, and when we add in the value represented
by the current character.
Detecting overflow during multiplication is not hard. Luckily, in the MIPS archi-
tecture, when multiplication and division are performed, the result is actually stored
in two 32-bit registers, named lo and hi. For division, the quotient is stored in lo
and the remainder in hi. For multiplication, lo contains the low-order 32 bits and
hi contains the high-order 32 bits of the result. Therefore, if hi is non-zero after we
do the multiplication, then the result of the multiplication is too large to fit into a
single 32-bit word, and we can detect the error.
We’ll use the mult instruction to do the multiplication, and then the mfhi (move
from hi) and mflo (move from lo) instructions to get the results.
To implement this we need to replace the single line that we used to use to do the
multiplication with the following:
# Note-- $t4 contains the constant 10.
mult $t2, $t4 # multiply $t2 by 10.
mfhi $t5 # check for overflow;
bnez $t5, overflow # if so, then report an overflow.
mflo $t2 # get the result of the multiply
There’s another error that can occur here, however: if the multiplication makes
the number too large to be represented as a positive two’s complement number, but
not quite large enough to require more than 32 bits. (For example, the number
3000000000 will be converted to -1294967296 by our current routine.) To detect
whether or not this has happened, we need to check whether or not the number in
register $t2 appears to be negative, and if so, indicate an error. This can be done by
adding the following instruction immediately after the mflo:
This takes care of checking that the multiplication didn’t overflow. We can detect
whether an addition overflowed in much the same manner, by adding the same test
immediately after the addition.
2.9. THE ATOI PROGRAM 41
The resulting code, along with the rest of the program, can be found in section 5.6
(on page 78).
42 CHAPTER 2. MIPS TUTORIAL
2.10 Exercises
2.10.1
In the palindrome algorithm 2.2, the algorithm for moving B to the end of the string
is incorrect if the string does not end with a newline.
Fix algorithm 2.2 so that it behaves properly whether or not there is a newline on
the end of the string. Once you have fixed the algorithm, fix the code as well.
2.10.2
Modify the palindrome.asm program so that it ignores whitespace, capitalization,
and punctuation.
Your program must be able to recognize the following strings as palindromes:
1. ”1 2 321”
2.10.3
Write a MIPS assembly language program that asks the user for 20 numbers, bub-
blesorts them, and then prints them out in ascending order.
Chapter 3
This chapter continues the tutorial for MIPS assembly language programming and
the SPIM environment1 . This chapter introduces more advanced topics, such as
how functions and advanced data structures can be implemented in MIPS assembly
language.
43
44 CHAPTER 3. ADVANCED MIPS TUTORIAL
The information that describes the state of a function during execution (i.e. the
actual parameters, the value of all of the local variables, and which statement is
being executed) is called the environment of the function. (Note that the values of
any global variables referenced by the function are not part of the environment.) For
a MIPS assembly program, the environment of a function consists of the values of all
of the registers that are referenced in the function (see exercise 3.3.1).
In order to implement the ability to save and restore a function’s environment,
most architectures, including the MIPS, use the stack to store each of the environ-
ments.
In general, before a function A calls function B, it pushes its environment onto
the stack, and then jumps to function B. When the function B returns, function
A restores its environment by popping it from the stack. In the MIPS software
architecture, this is accomplished with the following procedure:
1. The caller must:
(a) Put the parameters into $a0-$a3. If there are more than four parameters,
the additional parameters are pushed onto the stack.
(b) Save any of the caller-saved registers ($t0 - $t9) which are used by the
caller.
(c) Execute a jal (or jalr) to jump to the function.
2. The callee must, as part of the function preamble:
(a) Create a stack frame, by subtracting the frame size from the stack pointer
($sp).
Note that the minimum stack frame size in the MIPS software architecture
is 32 bytes, so even if you don’t need all of this space, you should still make
your stack frames this large.
(b) Save any callee-saved registers ($s0 - $s7, $fp, $ra) which are used by the
callee. Note that the frame pointer ($fp) must always be saved. The return
address ($ra) needs to be saved only by functions which make function calls
themselves.
(c) Set the frame pointer to the stack pointer, plus the frame size.
3. The callee then executes the body of the function.
4. To return from a function, the callee must:
3.1. FUNCTION ENVIRONMENTS AND LINKAGE 45
The convention used by the programs in this document is that a function stores
$fp at the top of its stack frame, followed by $ra, then any of the callee-saved registers
($s0 - $s7), and finally any of the caller-saved registers ($t0 - $t9) that need to be
preserved.
This definition leads directly to a recursive algorithm for computing the nth Fi-
bonacci number. As you may have realized, particularly if you’ve seen this sequence
before, there are much more efficient ways to compute the nth Fibonacci number.
Nevertheless, this algorithm is often used to demonstrate recursion– so here we go
again.
In order to demonstrate a few different aspects of the MIPS function calling con-
ventions, however, we’ll implement the fib function in a few different ways.
fib_return:
lw $ra, 28($sp) # restore the Return Address.
lw $fp, 24($sp) # restore the Frame Pointer.
lw $s0, 20($sp) # restore $s0.
lw $s1, 16($sp) # restore $s1.
lw $s2, 12($sp) # restore $s2.
addu $sp, $sp, 32 # restore the Stack Pointer.
jr $ra # return.
As a baseline test, let’s time the execution of this program computing the F (20):
3.1. FUNCTION ENVIRONMENTS AND LINKAGE 47
fib_return:
lw $ra, 28($sp) # Restore the Return Address.
lw $fp, 24($sp) # restore the Frame Pointer.
addu $sp, $sp, 32 # restore the Stack Pointer.
jr $ra # return.
Once again, we can time the execution of this program in order to see if this
change has made any improvement:
% echo 20 | /bin/time spim -file fib-t.asm
SPIM Version 5.4 of Jan. 17, 1994
Copyright 1990-1994 by James R. Larus ([email protected]).
All Rights Reserved.
See the file README a full copyright notice.
Loaded: /home/usr6/cs51/de51/SPIM/lib/trap.handler
10946
4.5 real 4.1 user 0.1 sys
In these tests, the user time is what we want to measure, and as we can see,
fib-s.asm is approximately 17% slower than fib-t.asm.
using any registers except $a0 and $v0. Therefore, we can postpone the work of
building a stack frame until after we’ve tested to see if we’re going to do the base
case.
In addition, we can further trim down the number of instructions that are executed
by saving fewer registers. For example, in the second recursive call to fib it is not
necessary to preserve n– we don’t care if it gets clobbered, since it isn’t used anywhere
after this call.
This is clearly much faster. In fact, it’s nearly twice as fast as the original
fib-s.asm. This makes sense, since we have eliminated building and destroying
about half of the stack frames, and a large percentage of the fib function does noth-
ing but set up and dismantle the stack frame.
Note that the reason that optimizing the base case of the recursion helps so much
with this algorithm is because it occurs about half of the time– but this is not charac-
teristic of all recursive algorithms. For example, in a recursive algorithm to compute
the factorial of n, the recursive case will occur about n − 1 times, while the base case
will only occur once. Therefore, it makes more sense to optimize the recursive case
in that situation.
There’s still more that can be done, however; see exercise 3.3.3 to pursue this
farther. A complete listing of a program that uses this implementation of the fib
function can be found in section 5.8 (on page 84).
Since we already have seen how to write functions (including recursive functions),
doing the inorder traversal won’t be much of a problem. Building the tree, however,
will require several new techniques: we need to learn how to represent structures
(in particular the structure of each node), and we need to learn how to dynamically
allocate memory, so we can construct binary trees of arbitrary size.
Needless to say, once you choose a representation you must fully comment it in
your code. In addition, any functions or routines that depend on the details of a
structure representation should mention this fact explicitly, so that if you change the
representation later you’ll know exactly which functions you will also need to change.
3.3 Exercises
3.3.1
In section 3.1, a function’s environment is defined to be the values of all of the registers
that are referenced in the function. If we use this definition, we may include more
registers than are strictly necessary. Write a more precise definition, which may in
some cases include fewer registers.
3.3.2
Write a MIPS assembly language program named fib-iter.asm that asks the user for
n, and then computes and prints the nth Fibonacci sequence using an O(n) iterative
algorithm.
3.3.3
The fib-o.asm program (shown in 3.1.1.3) is not completely optimized.
1. Find at least one more optimization, and time your resulting program to see if
it is faster than fib-o.asm. Call your program fib-o+.asm.
2. Since you know that fib will never call any function other than fib, can you
make use of this to optimize the calling convention for this particular function?
You should be able discover (at least) two instructions in fib that are not
necessary. With some thought, you may be able to find others.
Design a calling convention optimized for the fib program, and write a program
named fib-o++.asm that implements it. Time your resulting program and see
how much faster it is than fib-o.asm and your fib-o+.asm program.
3. Time the program from question 3.3.2 and compare times with fib-o.asm,
fib-o+.asm, and fib-o++.asm. What conclusion do you draw from your re-
sults?
3.3.4
Starting with the routine from atoi-4.asm, write a MIPS assembly language function
named atoi that behaves in the same manner as the atoi function in the C library.
54 CHAPTER 3. ADVANCED MIPS TUTORIAL
Your function must obey the MIPS calling conventions, so that it can be used in
any program. How should your function indicate to its caller that an overflow has
occurred?
3.3.5
Write a MIPS assembly language program that asks the user for 20 numbers, merge-
sorts them, and then prints them out in ascending order.
Chapter 4
55
56 CHAPTER 4. THE MIPS R2000 INSTRUCTION SET
• Few people were doing assembly language programming any longer if they could
possibly avoid it.
• Compilers for high-level languages only used a fraction of the instructions avail-
able in the assembly languages of the more complex architectures.
• Computer architects were discovering new ways to make computers faster, using
techniques that would be difficult to implement in existing architectures.
• If the op contains a (u), then this instruction can either use signed or unsigned
arithmetic, depending on whether or not a u is appended to the name of the
instruction. For example, if the op is given as add(u), then this instruction can
either be add (add signed) or addu (add unsigned).
• addr must be an address. See section 4.4.4 for a description of valid addresses.
4.4. THE MIPS INSTRUCTION SET 59
4.4.3.2 Jump
Op Operands Description
j label Jump to label lab.
jr src1 Jump to location src1.
jal label Jump to label lab, and store the address of the next in-
struction in $ra.
jalr src1 Jump to location src1, and store the address of the next
instruction in $ra.
4.4.4.1 Load
The load instructions, with the exceptions of li and lui, fetch a byte, halfword, or
word from memory and put it into a register. The li and lui instructions load a
constant into a register.
All load addresses must be aligned on the size of the item being loaded. For
example, all loads of halfwords must be from even addresses, and loads of words from
addresses cleanly divisible by four. The ulh and ulw instructions are provided to load
halfwords and words from addresses that might not be aligned properly.
62 CHAPTER 4. THE MIPS R2000 INSTRUCTION SET
Op Operands Description
◦ la des, addr Load the address of a label.
lb(u) des, addr Load the byte at addr into des.
lh(u) des, addr Load the halfword at addr into des.
◦ li des, const Load the constant const into des.
lui des, const Load the constant const into the upper halfword of des,
and set the lower halfword of des to 0.
lw des, addr Load the word at addr into des.
lwl des, addr
lwr des, addr
◦ ulh(u) des, addr Load the halfword starting at the (possibly unaligned)
address addr into des.
◦ ulw des, addr Load the word starting at the (possibly unaligned) ad-
dress addr into des.
4.4.4.2 Store
The store instructions store a byte, halfword, or word from a register into memory.
Like the load instructions, all store addresses must be aligned on the size of the
item being stored. For example, all stores of halfwords must be from even addresses,
and loads of words from addresses cleanly divisible by four. The swl, swr, ush and
usw instructions are provided to store halfwords and words to addresses which might
not be aligned properly.
Op Operands Description
sb src1, addr Store the lower byte of register src1 to addr.
sh src1, addr Store the lower halfword of register src1 to addr.
sw src1, addr Store the word in register src1 to addr.
swl src1, addr Store the upper halfword in src to the (possibly un-
aligned) address addr.
swr src1, addr Store the lower halfword in src to the (possibly unaligned)
address addr.
◦ ush src1, addr Store the lower halfword in src to the (possibly unaligned)
address addr.
◦ usw src1, addr Store the word in src to the (possibly unaligned) address
addr.
4.4. THE MIPS INSTRUCTION SET 63
Op Operands Description
rfe Return from exception.
syscall Makes a system call. See 4.6.1 for a list of the SPIM
system calls.
break const Used by the debugger.
nop An instruction which has no effect (other than taking a
cycle to execute).
64 CHAPTER 4. THE MIPS R2000 INSTRUCTION SET
.text addr The following items are to be assembled into the text
segment. By default, begin at the next available ad-
dress in the text segment. If the optional argument
addr is present, then begin at addr. In SPIM, the only
items that can be assembled into the text segment are
instructions and words (via the .word directive).
.kdata addr The kernel data segment. Like the data segment, but
used by the Operating System.
.ktext addr The kernel text segment. Like the text segment, but
used by the Operating System.
.extern sym size Declare as global the label sym, and declare that it is
size bytes in length (this information can be used by
the assembler).
4.8 Exercises
4.8.1
Many of the instructions available to the MIPS assembly language programmer are
not really instructions at all, but are translated by the assembler into one or more
instructions.
For example, the move instruction can be implemented using the add instruction.
Making use of register $0, which always contains the constant zero, and the fact that
the for any number x, x + 0 ≡ x, we can rewrite
as
Similarly, since either the exclusive or or inclusive or of any number and 0 gives
the number, we could also write this as either of the following:
or des, src1, $0
xor des, src1, $0
Show how you could implement the following instructions, using other instructions
in the native MIPS instruction set:
3. li des, const
Keep in mind that the register $at is reserved for use by the assembler, so you
can feel free to use this register for scratch space. You must not clobber any other
registers, however.
68 CHAPTER 4. THE MIPS R2000 INSTRUCTION SET
Chapter 5
The following sections include the source code for several of the programs referenced
by the tutorial. All of this source code is also available online.
For the convenience of the reader, the source code is listed here along with line
numbers in the left margin. These line numbers do not appear in the original code,
and it would be an error to include them in your own code.
69
70 CHAPTER 5. MIPS ASSEMBLY CODE EXAMPLES
5.1 add2.asm
This program is described in section 2.4.
5.2 hello.asm
This program is described in section 2.5.
5.3 multiples.asm
This program is described in section 2.7. The algorithm used is algorithm 2.1 (shown
on page 32).
39 b loop # iterate.
40 endloop:
41 la $a0, newline # print a newline:
42 li $v0, 4 # syscall 4 = print_string
43 syscall
44
45 exit: # exit the program:
46 li $v0, 10 # syscall 10 = exit
47 syscall # we’re outta here.
48
49 ## Here’s where the data for this program is stored:
50 .data
51 space: .asciiz " "
52 newline: .asciiz "\n"
53
54 ## end of multiples.asm
74 CHAPTER 5. MIPS ASSEMBLY CODE EXAMPLES
5.4 palindrome.asm
This program is described in section 2.8. The algorithm used is algorithm 2.2 (shown
on page 34).
5.5 atoi-1.asm
This program is described in section 2.9.1. The algorithm used is algorithm 2.3 (shown
on page 37).
39 syscall
40
41 exit: ## exit the program:
42 li $v0, 10 # load "exit" into $v0.
43 syscall # make the system call.
44
45 .data ## Start of data declarations:
46 newline: .asciiz "\n"
47 string_space: .space 1024 # reserve 1024 bytes for the string.
48
49 ## end of atoi-1.asm
78 CHAPTER 5. MIPS ASSEMBLY CODE EXAMPLES
5.6 atoi-4.asm
This program is described in section 2.9.4. The algorithm used is algorithm 2.3 (shown
on page 37), modified as described in section 2.9.4.
39
40 mult $t2, $t4 # multiply $t2 by 10.
41 mfhi $t5 # check for overflow;
42 bnez $t5, overflow # if so, then report an overflow.
43 mflo $t2 # get the result of the multiply
44 blt $t2, $0, overflow # make sure that it isn’t negative.
45
46 sub $t1, $t1, ’0’ # t1 -= ’0’.
47 add $t2, $t2, $t1 # t2 += t1.
48 blt $t2, $0, overflow
49
50 b sum_loop # and repeat the loop.
51 end_sum_loop:
52 mul $t2, $t2, $t3 # set the sign properly.
53
54 move $a0, $t2 # print out the answer (t2).
55 li $v0, 1
56 syscall
57
58 la $a0, newline # and then print out a newline.
59 li $v0, 4
60 syscall
61
62 b exit
63
64 overflow: # indicate that an overflow occurred.
65 la $a0, overflow_msg
66 li $v0, 4
67 syscall
68 b exit
69
70 exit: # exit the program:
71 li $v0, 10 # load "exit" into $v0.
72 syscall # make the system call.
73
74 .data ## Start of data declarations:
75 newline: .asciiz "\n"
76 overflow_msg: .asciiz "Overflow!\n"
77 string_space: .space 1024 # reserve 1024 bytes for the string.
78
79 ## end of atoi-4.asm
80 CHAPTER 5. MIPS ASSEMBLY CODE EXAMPLES
5.7 printf.asm
Using syscalls for output can quickly become tedious, and output routines can quickly
muddy up even the neatest code, since it requires several assembly instructions just
to print out a number. To make matters worse, there is no syscall which prints out a
single ASCII character.
To help my own coding, I wrote the following printf function, which behaves
like a simplified form of the printf function in the standard C library. It implements
only a fraction of the functionality of the real printf, but enough to be useful. See
the comments in the code for more information.
32 sw $s6, 0($sp)
33 addu $fp, $sp, 36
34
35 # grab the arguments:
36 move $s0, $a0 # fmt string
37 move $s1, $a1 # arg1 (optional)
38 move $s2, $a2 # arg2 (optional)
39 move $s3, $a3 # arg3 (optional)
40
41 li $s4, 0 # set # of formats = 0
42 la $s6, printf_buf # set s6 = base of printf buffer.
43
44 printf_loop: # process each character in the fmt:
45 lb $s5, 0($s0) # get the next character, and then
46 addu $s0, $s0, 1 # bump up $s0 to the next character.
47
48 beq $s5, ’%’, printf_fmt # if the fmt character, then do fmt.
49 beq $0, $s5, printf_end # if zero, then go to end.
50
51 printf_putc:
52 sb $s5, 0($s6) # otherwise, just put this char
53 sb $0, 1($s6) # into the printf buffer,
54 move $a0, $s6 # and then print it with the
55 li $v0, 4 # print_str syscall
56 syscall
57
58 b printf_loop # loop on.
59
60 printf_fmt:
61 lb $s5, 0($s0) # see what the fmt character is,
62 addu $s0, $s0, 1 # and bump up the pointer.
63
64 beq $s4, 3, printf_loop # if we’ve already processed 3 args,
65 # then *ignore* this fmt.
66 beq $s5, ’d’, printf_int # if ’d’, print as a decimal integer.
67 beq $s5, ’s’, printf_str # if ’s’, print as a string.
68 beq $s5, ’c’, printf_char # if ’c’, print as a ASCII char.
69 beq $s5, ’%’, printf_perc # if ’%’, print a ’%’
70 b printf_loop # otherwise, just continue.
71
72 printf_shift_args: # shift over the fmt args,
73 move $s1, $s2 # $s1 = $s2
74 move $s2, $s3 # $s2 = $s3
75
82 CHAPTER 5. MIPS ASSEMBLY CODE EXAMPLES
5.8 fib-o.asm
This program is described in section 3.1.1.3.
This is a (somewhat) optimized version of a program which computes Fibonacci
numbers. The optimization involves not building a stack frame unless absolutely
necessary. I wouldn’t recommend that you make a habit of optimizing your code in
this manner, but it can be a useful technique.
35 ## $t0 - parameter n.
36 ## $t1 - fib (n - 1).
37 ## $t2 - fib (n - 2).
38 .text
39 fib:
40 bgt $a0, 1, fib_recurse # if n < 2, then just return a 1,
41 li $v0, 1 # don’t build a stack frame.
42 jr $ra
43 # otherwise, set things up to handle
44 fib_recurse: # the recursive case:
45 subu $sp, $sp, 32 # frame size = 32, just because...
46 sw $ra, 28($sp) # preserve the Return Address.
47 sw $fp, 24($sp) # preserve the Frame Pointer.
48 addu $fp, $sp, 32 # move Frame Pointer to new base.
49
50 move $t0, $a0 # get n from caller.
51
52 # compute fib (n - 1):
53 sw $t0, 20($sp) # preserve n.
54 sub $a0, $t0, 1 # compute fib (n - 1)
55 jal fib
56 move $t1, $v0 # t1 = fib (n - 1)
57 lw $t0, 20($sp) # restore n.
58
59 # compute fib (n - 2):
60 sw $t1, 16($sp) # preserve $t1.
61 sub $a0, $t0, 2 # compute fib (n - 2)
62 jal fib
63 move $t2, $v0 # t2 = fib (n - 2)
64 lw $t1, 16($sp) # restore $t1.
65
66 add $v0, $t1, $t2 # $v0 = fib (n - 1) + fib (n - 2)
67 lw $ra, 28($sp) # restore Return Address.
68 lw $fp, 24($sp) # restore Frame Pointer.
69 addu $sp, $sp, 32 # restore Stack Pointer.
70 jr $ra # return.
71
72 ## data for fib-o.asm:
73 .data
74 newline: .asciiz "\n"
75
76 ## end of fib-o.asm
86 CHAPTER 5. MIPS ASSEMBLY CODE EXAMPLES
5.9 treesort.asm
This program is outlined in section 3.2. The treesort algorithm is given in algo-
rithm 3.1 (shown on page 51).
39 input_loop:
40 li $v0, 5 # syscall 5 == read_int.
41 syscall
42 move $s1, $v0 # $s1 = read_int
43
44 beq $s1, $s2, end_input # if we read the sentinel, break.
45
46 # tree_insert (number, root);
47 move $a0, $s1 # number= $s1
48 move $a1, $s0 # root = $s0
49 jal tree_insert # call tree_insert.
50
51 b input_loop # repeat input loop.
52 end_input:
53
54 ## Step 3: print out the left and right subtrees.
55 lw $a0, 4($s0) # print the root’s left child.
56 jal tree_print
57
58 lw $a0, 8($s0) # print the root’s right child.
59 jal tree_print
60
61 b exit # exit.
62 ## end of main.
63
64 ## tree_node_create (val, left, right): make a new node with the given
65 ## val and left and right descendants.
66 ## Register usage:
67 ## $s0 - val
68 ## $s1 - left
69 ## $s2 - right
70 tree_node_create:
71 # set up the stack frame:
72 subu $sp, $sp, 32
73 sw $ra, 28($sp)
74 sw $fp, 24($sp)
75 sw $s0, 20($sp)
76 sw $s1, 16($sp)
77 sw $s2, 12($sp)
78 sw $s3, 8($sp)
79 addu $fp, $sp, 32
80 # grab the parameters:
81 move $s0, $a0 # $s0 = val
82 move $s1, $a1 # $s1 = left
88 CHAPTER 5. MIPS ASSEMBLY CODE EXAMPLES
171
172 add_left:
173 sw $s2, 4($s1) # root->left = new_node;
174 b end_search_loop # goto end_search_loop;
175
176 go_right:
177 lw $s4, 8($s1) # ptr = root->right;
178 beqz $s4, add_right # if (ptr == 0) goto add_right;
179 move $s1, $s4 # root = ptr;
180 b search_loop # goto search_loop;
181
182 add_right:
183 sw $s2, 8($s1) # root->right = new_node;
184 b end_search_loop # goto end_search_loop;
185
186 end_search_loop:
187
188 # release the stack frame:
189 lw $ra, 28($sp) # restore the Return Address.
190 lw $fp, 24($sp) # restore the Frame Pointer.
191 lw $s0, 20($sp) # restore $s0.
192 lw $s1, 16($sp) # restore $s1.
193 lw $s2, 12($sp) # restore $s2.
194 lw $s3, 8($sp) # restore $s3.
195 lw $s4, 4($sp) # restore $s4.
196 addu $sp, $sp, 32 # restore the Stack Pointer.
197 jr $ra # return.
198 ## end of node_create.
199
200 ## tree_walk (tree):
201 ## Do an inorder traversal of the tree, printing out each value.
202 ## Equivalent C code:
203 ## void tree_print (tree_t *tree)
204 ## {
205 ## if (tree != NULL) {
206 ## tree_print (tree->left);
207 ## printf ("%d\n", tree->val);
208 ## tree_print (tree->right);
209 ## }
210 ## }
211 ## Register usage:
212 ## s0 - the tree.
213 tree_print:
214 # set up the stack frame:
5.9. TREESORT.ASM 91
259 exit:
260 li $v0, 10 # 10 is the exit syscall.
261 syscall
262 ## end of program!
263 ## end of exit.
264
265 ## Here’s where the data for this program is stored:
266 .data
267 newline: .asciiz "\n"
268 out_of_mem_msg: .asciiz "Out of memory!\n"
269
270 ## end of tree-sort.asm