Uploaded by misadoc13

Translating Functions

advertisement
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.
Download