MIPS Hello World
MIPS Hello World
# Hello, World!
.data ## Data declaration section
## String to be printed:
out_string: .asciiz "\nHello, World!\n"
MIPS assemblers support standard symbolic names for the general-purpose registers:
Example:
C code: a = b + c;
“The natural number of operands for an operation like addition is three…requiring every
instruction to have exactly three operands, no more and no less, conforms to the
philosophy of keeping the hardware simple”
Instructions "with overflow" will generate an runtime exception if the computed result is
too large to be stored correctly in 32 bits.
There are also versions of some of these that essentially ignore overflow, like addu.
C code: a = b + c + d;
Why?
Note that immediates cannot be used with all MIPS assembly instructions; refer to your
MIPS reference card.
Logical instructions also have three operands and the same format as the arithmetic
instructions:
Examples:
Example:
.data
N: .word 10
.text
main:
lw $t0, N # $t0 <-- Mem[N] (10)
la $t1, N # $t1 <-- N (address)
. . .
exit: li $v0, 10
syscall
In base + register mode the address is the sum of an immediate and the value in a
register:
j Label # PC = Label
b Label # PC = Label
jr $ra # PC = $ra
These are useful for building loops and conditional control structures.
int Sum = 0;
for (int i = 1; i <= N; ++i) {
Sum = Sum + i;
}
MIPS programmers are expected to conform to the following conventions when using the
29 available 32-bit registers:
Register 1 ($at) is reserved for the assembler, 26-27 ($k0, $k1) for operating system.
Registers 28-31 ($gp, $sp, $fp, $ra) are reserved for special uses, not user variables.
You may have noticed something is odd about a number of the MIPS instructions that
have been covered so far. For example:
li $t0, 0xFFFFFFFF
Now, logically there's nothing wrong with wanting to place a 32-bit value into one of the
registers.
But there's certainly no way the instruction above could be translated into a 32-bit
machine instruction, since the immediate value alone would require 32 bits.
In effect, the assembler supports an extended MIPS architecture that is more sophisticated
than the actual MIPS architecture of the underlying hardware.
Basic fact: at the machine language level there are no explicit data types, only
contents of memory locations. The concept of type is present only
implicitly in how data is used.
declaration: reserving space in memory, or deciding that a certain data item will reside
in a certain register.
A complete listing of MIPS/MARS directives can be found in the MARS help feature.
At the machine language level, there is generally little if any explicit support for
procedures. This is especially true for RISC architectures.
MIPS reserves register $31, aka $ra, to store the return address.
The called procedure must place the return value (if any) somewhere from which the
caller can retrieve it. The convention is that registers $v0 and $v1 can be used to hold
the return value. We will discuss what to do if the return value exceeds 4 bytes later…
Returning from the procedure requires transferring execution to the return address the
jal instruction placed in $ra:
jr $ra # PC = $ra
The called procedure can then access the parameters by following the same convention.
What if a parameter needs to be passed by reference? Simply place the address of the
relevant data object in the appropriate register, and design the called procedure to treat
that register value accordingly.
What if a parameter is smaller than a word? Clever register manipulation in the callee.
What if there are more than four parameters? We'll discuss that later…
Let's implement a MIPS procedure to get a single integer input value from the user and
return it:
get_integer:
# Prompt the user to enter an integer value. Read and return
# it. It takes no parameters.
jr $ra
Since this doesn't use any registers that it needs to save, there's no involvement with the
run-time stack.
Since the procedure does not take any parameters, the call is simple. The return value
will, by convention, have been placed in $v0.
. . .
.data # Data declaration section
prompt: .asciiz "Enter an integer value\n"
.text
Let's design a procedure to take some integer parameters and compute a value from them
and return that value. Say the expression to be computed is:
(a + b) – (c + d)
Then the caller needs to pass four arguments to the procedure; the default argument
registers are sufficient for this.
The procedure only returns a single one-word value, so the default register is enough.
The procedure will to use at least two registers to store temporary values while computing
the expression, and a third register to hold the final result.
The procedure will use $t0 and $t1 for the temporaries, and $s0 for the result.
(This could be done with fewer registers, but it's more illustrative this way.)
This time we must place the parameters (presumably obtained by calling the procedure
get_integer), into the default argument registers.
. . .
move $a0, $s0 # position the parameters
move $a1, $s1
move $a2, $s2
move $a3, $s3
jal proc_example # make the call
In addition to memory for static data and the program text (machine code), MIPS provides
space for the run-time stack (data local to procedures, etc.) and for dynamically-allocated
data:
Stack
Dynamic data
Static data $gp # ptr into global data
Dynamic data is accessed via pointers held by the program being executed, with addresses
returned by the memory allocator in the underlying operating system.
MIPS provides a special register, $sp, which holds the address of the most recently
allocated word on a stack that user programs can employ to hold various values:
Note that the run-time stack is "upside-down". That is, $sp, decreases when a value is
added to the stack and increases when a value is removed.
So, you decrement the stack pointer by 4 when pushing a new value onto the stack and
increment it by 4 when popping a value off of the stack.
activation record
or stack frame
for called
procedure
It is the responsibility of the called procedure to make sure that if it uses any of the
registers $s0 - $s7 it backs them up on the system stack first, and restores them before
returning.
In some situations, it is useful for the caller to also maintain the value that $sp held when
the call was made, called the frame pointer. The register $fp would be used for this
purpose.