Contents:

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
.data
str_hello: .asciiz "Hello!\n"
.text
	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!

But honestly? I just copy and paste these macros into most programs I write. Cause that is just tedious.

.macro print_str %str
	.data
	print_str_message: .asciiz %str
	.text
	la	a0, print_str_message
	li	v0, 4
	syscall
.end_macro

.macro println_str %str
	print_str %str
	li a0, '\n'
	li v0, 11
	syscall
.end_macro

.globl main
main:
	println_str "Hello, world!"

Using (Global) Variables

See the correspondences section on variables.


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.
        print_int(my_array[i]);
    }
}

Doing Arithmetic

The CPU can only do one operation at a time. So, write out your algebraic expression, and use order-of-operations to determine the sequence of operations you need to perform.

Examples:

	# x = y + z
	# we just need to do one 'add' here.

	lw  t0, y       # t0 = y
	lw  t1, z       # t1 = z
	add t0, t0, t1  # t0 = y + z
	sw  t0, x       # store y + z into x

	# ---------------------------------------
	# x = x / 2 + 3 * y - z
	# 1. divide, 2. multiply, 3. add, 4. subtract.
	# it's helpful to make notes of what is in which registers as you go.
	# notice how the value kind of "builds up" in t0. like a snowball.

	lw  t0, x      # t0 = x
	div t0, t0, 2  # t0 = x / 2
	lw  t1, y      # t1 = y
	mul t1, t1, 3  # t1 = y * 3
	add t0, t0, t1 # t0 = x / 2 + 3 * y
	lw  t1, z      # t1 = z
	sub t0, t0, t1 # t0 = x / 2 + 3 * y - z
	sw  t0, x      # store the final result into x!

Writing functions

For a foolproof way to write functions, do this:

  1. Start with this skeleton code:

     function_name:
         push ra
         # --------------------------------
    
         # --------------------------------
     _function_name_end:
         pop ra
         jr ra
    
  2. Write code between the ---- lines above. The pushes and pops are like the {} on a function.
    • Arguments are already in the a registers when the function starts.
    • To return early, jump/branch to _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 going to _function_name_end.
  3. If you need to use any s registers, push them at the beginning, and pop them in reverse order at the end:

     function_name:
         push ra
         push s0 # think of this as declaring "I need s0 for this function."
         push s1 # "
         # --------------------------------
    
         # now we can use s0 and s1 with no problem.
    
         move s0, a0 # need to save a0 if we `jal` to another function
         li s1, 0    # a loop counter?
    
         # ... blah blah more code...
    
         # --------------------------------
     _function_name_end:
         # all pops in REVERSE order.
         pop s1
         pop s0
         pop ra
         jr ra