Printing strings and characters

Strings are many bytes long and cannot fit in registers. So, we put them in the data segment, and then refer to them by their address (location in memory).

For example, print_string("Hello!\n") would look like:

# you can switch to .data anywhere you want, not just at the top of the file
str_hello: .asciiz "Hello!\n"
	la a0, str_hello # get the string's address
	li v0, 4         # v0 = 4 for print_string
	syscall          # print it!

Single characters do fit in a register, and you can use li to put them there.

For example, print_char('\n') will print a newline, and it would look like:

	li a0, '\n' # characters are integers, so this is fine
	li v0, 11   # v0 = 11 for print_char
	syscall     # print it!

Using (Global) Variables

Globals are stored in memory. First, you need to declare the variable in the data segment, like this:

	# this is like: int my_var = 10;
	my_var: .word 10

# don't forget to switch back to the text segment

To access this variable, you must use load instructions to get its value, and store instructions to set its value.

For example: the equivalent of print_int(my_var) would look like:

	lw a0, my_var # get the variable's value
	li v0, 1      # v0 = 1 for print_int
	syscall       # run the syscall

You can change its value with a store instruction. my_var = 20 would look like:

	li t0, 20     # must put the value in a register
	sw t0, my_var # copy that 20 into my_var

Read-modify-write operations, like incrementing, require a load and a store. my_var++ would look like:

	lw  t0, my_var # get the value
	add t0, t0, 1  # add 1 to it
	sw  t0, my_var # and put the incremented value back into it

Choosing registers

It’s way less complicated than you are thinking.

  1. If you are about to call a function, its arguments go into a registers.
    • Start at a0, then a1 etc
  2. If you are about to return a value from a function, the return value goes into v0.
    • Also, if you are about to do a syscall, the syscall number goes into v0. idk why.
  3. Otherwise ask yourself this question: do I need to access this value again after a jal?
    • No? Use a t register.
      • Most values only have to be in a register for a brief time.
      • It’s common to use these for calculations, variable access, intermediate values etc.
      • Reuse the registers. You don’t have to do t0, t1, t2, t3, t4, t5, t6 etc. I rarely need to use beyond t2 because I rarely need to be using more than 3 “things” at once.
    • Yes? use an s register.
      • It’s common to use these as loop counters and other “local variable” tasks.
      • Be sure to push and pop them as described in “writing functions” below.

If that question in 3 doesn’t make sense, think of it in Java terms:

void func() {
    // i is a local variable. I should probably use an s register to represent it.
    for(int i = 0; i < 10; i++) {
        // the address of this array is only used once, within this line, and never again.
        // I should probably use a t register to load its address.

        // also, since the value from the array is the first argument to print_int,
        // I should load that value into a0.

Writing functions

For a foolproof way to write functions, do this:

  1. Start with this skeleton code:

         push ra
         pop ra
         jr ra
  2. Write code between the push and pop.
    • Arguments are already in the a registers.
    • To return, use j function_name_end. DO NOT just throw a jr ra in the middle of the function.
    • To return a value, put a value in v0 before returning.
  3. If you need to use any s registers, **push them at the beginning, and pop them in reverse order at the end:

         push ra
         push s0
         push s1
         li s0, 0
         li s1, 1
         # ... blah blah more code...
         # all pops in REVERSE order
         pop s1
         pop s0
         pop ra
         jr ra