Translating a Java Procedure To MIPS For: CDA-3103, Spring 2019 In this step-by-step tutorial, we will translate a simple Java function body and call to MIPS. Our prototype Java code will be: public int f (int x) { int w = 5; return w-x; } public static void main(String[] args) { ... int z = f(y+1); ... } This code, although straightforward and not the most useful, contains all of the essential elements of a procedure call and body. These include parameters (x), a return value assigned to a local variable (z), and local variables inside the function (w). The most fundamental concept to keep in mind is that the body must be translated separately from the call. In other words, f() cannot have any knowledge of who is calling it (because in reality, anybody can). Similarly, when I call f() from main(), I have no knowledge of the body of f, other than what parameters it accepts. Therefore, it is best practice to translate one, than translate the other. Therefore I have divided this document into Part A (The function body) and Part B (The function call). Part A. The Function Body In Part A, we will translate the body of the function f. Functions, similar to branch targets, should be specified using labels as shown below: Java MIPS public int f (int x) { int w = 5; return w-x; } f: public static void main(String[] args) { ... int z = f(y+1); ... } main: Variable Registers Since f cannot assume anything about who is calling it, we must specify registers for variables as though f is the only code we are translating. Therefore the first local variable (w) should be assigned to register $s0. If a variable is an argument (i.e. x), we already know its register. Argument are assigned to $a registers automatically, in order. Therefore we can take it for granted that x is assigned to $a0. Had there been more arguments, they would have been assigned to $a1, $a2, $a3. The same logic must be applied when we start translating main. y (the first local variable) must be assigned to $s0. Although this will create a conflict -- fundamentally, we cannot assume we have any knowledge that there is a function f that also uses $s0. Remember that f() could be written by anybody - think of a function like Math.sqrt(). We have no idea what local variables are in that function, or how many. The return value also has its own register ($v0). This gives us enough information to translate the Java code inside the function. We also know where to return to, which is provided in the return address register ($ra). We have a jump (j) instruction that can go to a target, but we also have a jump register instruction (jr) that can go to a register. We can also trivially translate the int y = 3; in main(), but will table the function call until Part B: Java MIPS public int f (int x) { int w = 5; return w-x; } f: public static void main(String[] args) { int y = 3; int z = f(y+1); ... } main: addi $s0, $zero, 3 addi $s0, $zero, 5 sub $v0, $s0, $a0 jr $ra # w = 5 # return w-x # go back to main # y = 3 We will now work out the conflict with $s0, which arises because we use $s0 to represent y in main(), and w in f(). This means that the moment we set w ($s0) to 5 in f, we lose y . The trick, then, is to do the following: 1. Before running any code in f, save $s0 (y) someplace safe. 2. Run the code in f, and freely use $s0 to hold w. 3. Before returning from f, reload $s0 from that same safe place. This should be done for any local variables that f uses (in this case there was just one). MIPS provides a register ($sp) that holds an address for safe space, called the runtime stack. I will lecture on the runtime stack and provide a visual representation of what is happening under the hood; this is mainly intended as a guide. Saving and reloading old $s registers We must save the old $s0 (y) before running any code for f. We therefore have no option but to do this at the top of f: Java MIPS public int f (int x) { int w = 5; return w-x; } f: addi $sp, $sp, -4 # Budget 4 bytes(1 int) sw $s0, 0($sp) # Put y ($s0) there addi $s0, $zero, 5 # w = 5 sub $v0, $s0, $a0 # return w-x jr $ra # go back to main public static void main(String[] args) { int y = 3; int z = f(y+1); main: addi $s0, $zero, 3 ... } # y = 3 The amount of space we budget depends on how many integer variables we are saving, which is the same as how many local variables f has. In this case there was just one ($s0), so we needed 4*1 = 4 bytes (remember every integer is four bytes). With this sw instruction, y ($s0) now safely resides at RAM[0+$sp]. We can now go ahead and use $s0 for w, since y is saved. We just now must reload y before we return with the jr. This is done by reversing the instructions that save y: Java MIPS public int f (int x) { int w = 5; return w-x; } f: addi $sp, $sp, -4 # Budget 4 bytes(1 int) sw $s0, 0($sp) # Put y ($s0) there addi $s0, $zero, 5 # w = 5 sub $v0, $s0, $a0 # return w-x lw $s0, 0($sp) # Get y ($s0) back public static void main(String[] args) { addi $sp, $sp, 4 # Reclaim 4 bytes (1 int) int y = 3; jr $ra # go back to main int z = f(y+1); ... } main: addi $s0, $zero, 3 # y = 3 Now we can count on $s0 holding the value of y (and not w) when we jump back to main. (Remember: Even in your intro course, you have seen this. I did not need to choose different variables w and y above, I could have just as easily used y both places. f and main would each hold their own copy of y in that situation). Part B. The Function Call Function call and arguments We now have only one more statement to translate: The call to f() in main. This function call passes a single parameter (y+1) and assigns the return value to z. Recall that when we wrote f, we assumed the parameter x to be in $a0. In the function call, it is our job to place the value we are passing (y+1) into $a0. We do this first: Java MIPS public int f (int x) { int w = 5; return w-x; } f: addi $sp, $sp, -4 # Budget 4 bytes (1 int) sw $s0, 0($sp) # Put y ($s0) there addi $s0, $zero, 5 # w = 5 sub $v0, $s0, $a0 # return w-x lw $s0, 0($sp) # Get y ($s0) back public static void main(String[] args) { addi $sp, $sp, 4 # Reclaim 4 bytes (1 int) int y = 3; jr $ra # go back to main int z = f(y+1); ... main: addi $s0, $zero, 3 # y = 3 } addi $a0, $s0, 1 # first param=y+1 We call the function f using a jump-and-link instruction (jal), which works like a normal jump except that it saves the return address in $ra (which recall, we assumed above): Java MIPS public int f (int x) { int w = 5; return w-x; } f: addi $sp, $sp, -4 # Budget 4 bytes (1 int) sw $s0, 0($sp) # Put y ($s0) there addi $s0, $zero, 5 # w = 5 sub $v0, $s0, $a0 # return w-x lw $s0, 0($sp) # Get y ($s0) back addi $sp, $sp, 4 # Reclaim 4 bytes (1 int) jr $ra # go back to main public static void main(String[] args) { int y = 3; int z = f(y+1); ... } main: addi $s0, $zero, 3 # y = 3 addi $a0, $s0, 1 # first param=y+1 jal f # call f Finally, we need to assign the return value of f to a variable z. Since z is the second local variable defined, we will use $s1 to hold z. As we recall, we placed the return value into $v0 when we translated f, and so can assume that register has the right value and assign it to z: Java MIPS public int f (int x) { int w = 5; return w-x; } f: addi $sp, $sp, -4 # Budget 4 bytes (1 int) sw $s0, 0($sp) # Put y ($s0) there addi $s0, $zero, 5 # w = 5 sub $v0, $s0, $a0 # return w-x lw $s0, 0($sp) # Get y ($s0) back public static void main(String[] args) { addi $sp, $sp, 4 # Reclaim 4 bytes (1 int) int y = 3; jr $ra # go back to main int z = f(y+1); ... main: addi $s0, $zero, 3 # y = 3 } addi $a0, $s0, 1 # first param=y+1 jal f # call f add $s1, $v0, $zero # z = f’s ret val We now, however, have created a problem with main’s parameter “args”, which resides in register $a0 (we just changed that to y+1). In addition when we do jal, that modifies $ra. But main() is a function also, which means we lost its $ra. Saving and reloading $a registers, $ra We therefore take a similar approach, before modifying $a0 or $ra, we must save them to RAM. In general, we need to save any arguments that main() (or the calling procedure) has, in this case there is just one (“args”). Java MIPS public int f (int x) { int w = 5; return w-x; } f: addi $sp, $sp, -4 # Budget 4 bytes (1 int) sw $s0, 0($sp) # Put y ($s0) there addi $s0, $zero, 5 # w = 5 sub $v0, $s0, $a0 # return w-x lw $s0, 0($sp) # Get y ($s0) back public static void main(String[] args) { addi $sp, $sp, 4 # Reclaim 4 bytes (1 int) int y = 3; jr $ra # go back to main int z = f(y+1); ... main: addi $s0, $zero, 3 # y = 3 } addi $sp, $sp, -8 # Budget 8 B (2 ints) sw $a0, 0($sp) # Put args ($a0) there sw $ra, 4($sp) # Put $ra there addi $a0, $s0, 1 # first param=y+1 jal f # call f add $s1, $v0, $zero # z = f’s ret val In this case we must budget space for two integers (8 bytes). $a0 can go at RAM[0+$sp]. $ra would then be four bytes down (RAM[4+$sp]). We then reload them from the exact same spots, right after the jal finishes: Java MIPS public int f (int x) { int w = 5; return w-x; } f: addi $sp, $sp, -4 # Budget 4 bytes (1 int) sw $s0, 0($sp) # Put y ($s0) there addi $s0, $zero, 5 # w = 5 sub $v0, $s0, $a0 # return w-x lw $s0, 0($sp) # Get y ($s0) back addi $sp, $sp, 4 # Reclaim 4 bytes (1 int) jr $ra # go back to main public static void main(String[] args) { int y = 3; int z = f(y+1); ... } main: addi $s0, $zero, 3 # y = 3 addi $sp, $sp, -8 # Budget 8 B (2 ints) sw $a0, 0($sp) # Put args ($a0) there sw $ra, 4($sp) # Put $ra there addi $a0, $s0, 1 # first param=y+1 jal f # call f lw $a0, 0($sp) # Get args ($a0) back lw $ra, 4($sp) # Get $ra back addi $sp, $sp, 8 # Reclaim 8 B (2 ints) add $s1, $v0, $zero # z = f’s ret val General Procedure: Function Body and Call Although this may seem like a lot to remember, the general procedure is going to be the same independent of the function you are translating. We can generalize it as follows: Java MIPS function { Function Body Return Statement } function: Save any $s registers it uses Translate Function Body as normal Put return value in $v0 Reload any $s registers it uses jr $ra caller { Statements Call to Function } caller: Translate Statements as normal Save $a registers and $ra Change $a registers for Function jal to Function Reload $a registers and $ra If you apply this template to any function and any caller, you will not go wrong (even if the function is recursive!) Hopefully this is helpful. Please let me know if you have questions.