Q:

Save registers on a stack in a procedure call in Assembly

I'm taking an intro course on Assembly and I encountered the following code. I'm not sure how stacks work here. This is the C code that's supposed to be translated into Assembly: (Sorry I forgot to mention we are using MIPS)
int leaf_example (int g, h, i, j) {
     int f;
     f = (g + h) - (i + j);
     return f;
}

In class, the professor says the correct translation should be: (I added my questions in the comments)
leaf_example:
   addi $sp, $sp, -12 # Why do we need to allocate 3 bytes for each register?
   sw $t1, 8($sp) # I don't really understand the meaning of this line and the next two lines
   sw $t0, 4($sp)
   sw $s0, 0($sp)
   add $t0, $a0, $a1
   add $t1, $a2, $a3
   sub $s0, $t0, $t1
   add $v0, $s0, $zero
   lw $s0, 0($sp) # After we sw the registers on the stack, why do we need to load them back?
   lw $t0, 4($sp)
   lw $t1, 8($sp)
   addi $sp, $sp, 12
   jr $ra

I think I have a vague understanding of how $s registers differ from $t registers: when calling a function, contents of $s registers stays the same, but contents of $t registers are not guaranteed to stay the same. But how does this fact relate to pushing the registers onto the stack? Thank you very much for your help!

A:

There are no guarantees, only conventions. You can write a function that destroys $s registers or does other bad things. The convention is to preserve $s0-$s7, $gp, $sp and $fp. Failing to do so will most likely result in a function up the call chain failing in some way (and in points deducted, if it's graded).
So if you're going to use $s registers, you must back them up somehow, and restore them before returning.
That's what the code is doing, though calling that "the correct translation" is misleading in my opinion. It's one way. But it also preserves $t registers while it doesn't have to. It could also save $s0 in a $t register. It could even not use $0 in the first place, which is even simpler. It puts a result in $s0 only to immediately move it to $v0 anyway, it could just have put it there directly and then everything becomes easy:
leaf_example:
   add $t0, $a0, $a1
   add $t1, $a2, $a3
   sub $v0, $t0, $t1
   jr $ra

No more cruft.
But perhaps the goal was to show how leaf functions work in general. Or perhaps you have to strictly follow some specific translation rules, for example always specifically allocating a register for a local variable and always generating a move to $v0 for a return x statement.

