You are on page 1of 53

COMPX203 Computer Systems

Subroutines and Stacks


Recap
• From the last two lectures:
• WRAMP assembly files
• Directives
• Labels
• Instructions
WRAMP Subroutines and Stacks
Subroutines
• Subroutines allow programs to be well organised.
• Abstraction:
• Break a complex problem into simpler parts.
• Code Reuse:
• No need to write the same (or very similar) code
multiple times.
• Overall, effective use of subroutines makes
programs easier to write, debug, and maintain.
Subroutines
• High-level programming languages provide a way to:
• Jump to a subroutine
• Pass parameters to a subroutine
• Store local variables for a subroutine
• Return from a subroutine (with or without a return value)

int pythagoras(int a, int b) int sqrt(int n)


{ {
int temp=0, res=0; int result = 0;
temp += a * a; int temp = 1 << 30;
temp += b * b;
res = sqrt(temp); /* magic */
return res;
} return result;
}
WRAMP Subroutines
• WRAMP assembly includes instructions to:

• Jump and Link (jal subroutine)


• Saves the address of the next instruction (after jal) to $ra
• Jumps to the address of the subroutine

• Jump to Register (jr $register)


• Jumps to an address that is stored in a register (normally $ra)
• This does not change the value of the register
WRAMP Subroutines: Example
Address  32 bits 

main: 0x00000 add $1, $0, $0

0x00001 jal 0x00004


add $1, $0, $0
0x00002 jal 0x00004
jal incrementBy1 0x00003 syscall
jal incrementBy1 0x00004 addi $1, $1, 1

syscall 0x00005 jr $ra

??

incrementBy1: ??

??
addi $1, $1, 1
??
jr $ra ??

??

$1 = ? $ra = ?
Memory (RAM)
WRAMP Subroutines: Example 2
Address  32 bits 

main: 0x00000 add $1, $0, $0

add $1, $0, $0 0x00001 jal 0x00003

jal incrementBy2 0x00002 syscall

syscall 0x00003 jal 0x00006

0x00004 jal 0x00006

incrementBy2: 0x00005 jr $ra

jal incrementBy1 0x00006 addi $1, $1, 1

jal incrementBy1 0x00007 jr $ra

jr $ra ??
… ??
incrementBy1: ??
addi $1, $1, 1 ??
jr $ra …
Memory (RAM)
Multiple Subroutines
• WRAMP only has one return address register.
• Calling another subroutine will overwrite its value.
• If that happens, we lose the original value and
hence cannot return to the original caller.
• We become lost 
Solution?
• We could save the value of $ra before using jal
• How about choosing a register that we aren’t using,
and simply store a copy of $ra there?
• Example: we’re not using $9 yet…
incrementBy2:
add $9, $0, $ra #copy $ra to $9
jal incrementBy1 #overwrites $ra
jal incrementBy1
add $ra, $0, $9 #copy $9 back to $ra
jr $ra #return to caller!
A Good Solution?
• Not really…
• We would quickly run out of registers for anything but
trivial programs.
• Does not easily support recursion.
• Sometimes we don’t actually know which registers are
being used elsewhere.
• What if we link with someone else’s code/library?
• Saving the return address to another register is not
a general solution.
Diversion: Stacks
“jal talkAboutStacks”
Stacks
• Last In, First Out (LIFO).
• Often used in programming when
data will need to be used in the
reverse order to its creation.
• Only two operations are supported:
• Push an item onto the top of the stack
• Pop (retrieve) an item from the top of
the stack
Stack Implementation
• A stack can be implemented in several ways,
including the use of:
• An array
• A linked list
• With an array implementation, we need to keep
track of the top of the stack.
• A pointer is a convenient way to do this.
Pointers
• Pointers are variables that hold an address rather
than a value.
• Can be stored in registers or memory.
• If a register/memory location holds the address of
another variable, we can say it points to that variable
• E.g. “Register $1 points to address 2” 0
1
• What if we increment $1 by 1? 2
$1 2 3
4
5
Memory (RAM)
Pointers
• Pointers are variables that hold an address rather
than a value.
• Can be stored in registers or memory.
• If a register/memory location holds the address of
another variable, we can say it points to that variable
• E.g. “Register $1 points to address 2” 0
1
• What if we increment $1 by 1? 2
$1 3 3
4
5
Memory (RAM)
Pointers
• Simple concept, but very powerful.
• In practice, they require careful management:
• Have to make sure they always point to something valid,
otherwise very bad things can happen.
• Can have pointers to pointers to pointers…
• “Dangerous” if not used correctly:
• Can point to any memory within your process.
• Be careful not to corrupt the wrong areas of memory.
• For that reason, many languages like C# and Java do not
provide direct support for pointers.
Example: Push
Stack, initially empty Stack, after “PUSH 20”

$sp $sp

20

Note: addresses not shown for clarity


Example: Push (again)
Then “PUSH 5” And a third “PUSH 10”

$sp $sp

10
5 5
20 20
Example: Pop
Before “POP” After “POP”

$sp $sp

10 10
5 5
20 20
Note: popping an item off the stack means moving the pointer to the second-to-top
item. There is no need to explicitly delete the data, because it will get overwritten in
the next push.
Example: Pop (again)
Before a second “POP” After a second “POP”

$sp $sp

10 10
5 5
20 20
WRAMP Implementation 31 0

0x00000
0x00001
Program
• The system stack is 0x00002 Instructions

located at the highest 0x00003

memory addresses, and


Program
grows “downwards” Data

• i.e. it grows towards



memory address zero.

• The register $sp is used 0x1FFFC


to point to the top word 0x1FFFD
System
Stack
on the stack. 0x1FFFE
0x1FFFF
WRAMP Implementation
Example: push the value of $2 onto the stack

subui $sp, $sp, 1 # Move the top of the


# stack "up" by one word

sw $2, 0($sp) # Store the value of $2


# at the new top of the
# stack
WRAMP Implementation
Example: pop a value off the top of the stack into $5

lw $5, 0($sp) # Load the value sitting


# on the top of the stack

addui $sp, $sp, 1 # Move the top of the


# stack "down" by one word

Note: if we just want to remove a word from the stack and


don’t care about its value, the lw instruction is unnecessary.
WRAMP Implementation
• Note that these examples do not provide any form
of boundary checking:
• If we keep pushing things onto the stack, the stack
pointer would soon be pointing at something it should
not be pointing at!
• We would eventually overwrite part of our program.
• A robust program must:
• Ensure the stack is not already full before pushing a new
value onto the stack
• Ensure the stack is not already empty before popping a
value off the stack
More WRAMP Stacks
• There is only one $sp, and only one system stack
• What if you need another stack?
• Implement it yourself!
.data
stackPointer:
.word stack # Initialise the stack
# pointer to the address
# of the allocated space
.bss
.space 100 # Allocate space for the stack
stack: # Why is this below the .space?
More WRAMP Stacks
• The stack pointer for our new stack is located in
memory (RAM) rather than a register, like $sp
• To use it, we first have to load it into a register.
• Example: push the value of $2 onto the stack, using
$1 as a temporary stack pointer.
lw $1, stackPointer($0) # Load into $1
subui $1, $1, 1 # Move "up" by 1 word
sw $1, stackPointer($0) # Save updated stack
# pointer back to RAM
sw $2, 0($1) # Store the value of
# $2 at top of stack
Stack Summary
• A Stack is a way to store data in a Last In, First Out
(LIFO) fashion.
• A Push operation adds an item onto the top of the
stack.
• A Pop operation removes an item from the top of
the stack.
• Can be implemented using a pointer to keep track
of where the top is located in memory.
[SideBar]
For you to do: What Next?
• Read Chapter 2 of the WRAMP Manual
• Make a start on the second lab exercise
• Available in Moodle
• Like the first exercise, there is a programming part and
an online quiz
Back to Subroutines…
jr $ra
Saving the Return Address $ra
• Recall that we need to save the return address
before calling another subroutine.
• Otherwise jal will overwrite it, and we won’t be able
to return to our caller.
• Copying the return address to another register
could work, but is not a general solution.
• What else could be done?
Saving $ra using the Stack
incrementBy2:
subui $sp, $sp, 1 #Push value of $ra
sw $ra, 0($sp) #onto the stack

jal incrementBy1 #Overwrites $ra


jal incrementBy1

lw $ra, 0($sp) #Pop (restore) the


addui $sp, $sp, 1 #value of $ra

jr $ra #Now returns to


#the correct place
Saving Registers
• The reason we need to save $ra is because there is
only one of them, hence it needs to be shared.
• We also have to share the other registers across the
whole program.
• How could we do this?
• Use different registers in each subroutine?
• Allocate some memory in .data or .bss for each
subroutine and save a copy of register values there?
• Something else?
Saving Registers
• Use different registers in each subroutine?
• We will soon run out of registers…

• Allocate some memory in .data or .bss for


each subroutine to save a copy of register values?
• Could work, but this requires that memory is reserved
even when the subroutine isn’t active.
• Does not support recursion.

• Something else?
Saving Registers using the Stack
subroutine:
subui $sp, $sp, 1 #Push value of $ra
sw $ra, 0($sp) #onto the stack

subui $sp, $sp, 1 #Push value of $5 ($5 just as e.g.)


sw $5, 0($sp) #onto the stack

# We are now able to use $5 and jal ($ra)


# freely without losing their previous values

lw $5, 0($sp) #Pop (restore) the


addui $sp, $sp, 1 #value of $5

lw $ra, 0($sp) #Pop (restore) the


addui $sp, $sp, 1 #value of $ra

jr $ra #Now appears to return without


#modifying $5 or $ra
Saving Registers using the Stack
• Note that the previous example has a few duplicate
instructions:
• subui $sp, $sp, 1
• addui $sp, $sp, 1
• This can be optimised by calculating how many
registers we need to save, and pushing/popping a
block of registers all at once.
• More efficient, and more readable!
Relative Addressing
• Say we need to save/restore a block
of four registers.
• subui $sp, $sp, 4 will move
the top of the stack up by four
words, effectively pushing a block of -2
four words onto the stack. -1
$sp + 0 Backup of $ra
• We can then access words within
that block by using constant offsets, +1 Backup of $5
+2 Backup of $6
relative to $sp
+3 Backup of $7
• Increasingly positive offsets refer to +4
words further down the stack. +5
Memory (RAM)
Saving Registers using the Stack (again)
subroutine:
subui $sp, $sp, 4 #Push a block for 4 registers
sw $ra, 0($sp) #Store value of the first one
sw $5, 1($sp) #Store value of the second one
sw $6, 2($sp) #Store value of the third one
sw $7, 3($sp) #Store value of the fourth one

# We are now able to use $5, $6, $7, and jal ($ra)
# freely without losing their previous values

lw $7, 3($sp) #Restore value of $7


lw $6, 2($sp) #Restore value of $6
lw $5, 1($sp) #Restore value of $5
lw $ra, 0($sp) #Restore value of $ra
addui $sp, $sp, 4 #Pop the whole block

jr $ra #Now appears to return without


#modifying $5, $6, $7, or $ra
Local Variables
• The most useful place to store local variables is in
registers; it is fastest and easiest to access them
there.
• However, most programs will need more variables
than there are registers.
• Static memory allocation (in .data and .bss) is
not useful, since it only allows one block of
memory per subroutine and hence cannot support
recursion.
• One solution is – again – to use the stack!
Summary (so far…)
• The stack offers subroutines a convenient way to save
and restore the values held in registers.
• Saving and restoring $ra allows subroutine calls to be
multiple levels deep.
• Saving and restoring other registers creates the illusion
that each subroutine has its own independent set of
registers and local variables.
• e.g., modifying $5 in one subroutine won’t affect $5 in
another subroutine, despite the CPU having only one physical
$5
• For this to be effective, all subroutines must save any
registers that they modify, and restore their original values
before returning.
Application Binary Interfaces (ABIs)
• Several questions remain:

How do we return a value from a subroutine?

How do we pass parameters to a subroutine?


Application Binary Interfaces (ABIs)
• There are many valid ways to do these things.
• However, to ensure that our code works correctly
with libraries and code generated by a compiler, we
have to follow a standard set of conventions.
• These conventions are referred to as an Application
Binary Interface (ABI).
• The following slides discuss the ABI used in
WRAMP assembly.
Return Value
• Example from the first lab: readswitches
• After the subroutine returns, the value of the switches
will be held in register $1
• $1 does not need to be saved and restored using
the stack, because it is expected to be modified by
the subroutine.
• Side benefit: a subroutine is therefore free to
modify $1 even if it has no return value.
Parameters
• Example from the first lab: writessd.
• The value to be displayed on the SSDs is held in
register $2 prior to calling the subroutine.
• Passing parameters in registers is fast, but often
limited:
• WRAMP only has 12 usable registers after excluding $0,
$1, $sp, and $ra
• These registers also need to be used for purposes other
than passing parameters, e.g. local variables.
• The library used in lab 1 does not follow the
standard WRAMP ABI for passing parameters.
Parameters
• A convention is required to allow an arbitrary numbers
of parameters to be passed to a subroutine.
• Yet again, the system stack offers a convenient way to
do this.
• Parameters are pushed onto the stack before a
subroutine is called, with the first parameter being
located on the top of the stack, and others follow
underneath.
• Hence, by this convention, $sp will be pointing to the
first parameter when a subroutine is first entered.
Stack Frame
• The system stack is used for:
• Saving registers (including $ra)
• Storing local variables
• Passing parameters
• We can combine all these things into a block of
memory called a stack frame.
• In a subroutine, we can push the entire stack frame
onto the stack at once.
• Or – more accurately – reserve space for the whole stack
frame.
Stack Frame

Parameters to be
passed to subroutines
called by this subroutine
Register values, saved
before being modified
by this subroutine
Local variables used
by this subroutine
$sp points to the top of this stack frame, i.e. the lowest
memory address that is part of this stack frame.
Stack Frame

Stack frame for this


subroutine

Parameters passed to this


subroutine

Stack frame of the


calling subroutine
Stack Frame Size
• How big should the stack frame be?
• Enough space to store the maximum number of
parameters required by subroutines that we call.
• The number of parameters passed to this subroutine is
the responsibility of the caller.
• Enough space to save all registers that we use,
including $ra but excluding $1 and $sp.
• Enough space to store any local variables that we
can’t fit into registers.
frame size = args + regsave + locals
Example Subroutine
int multiply(int one, int two) Registers:
{ $4 result
int i; $5 i
int result = 0; $ra for calling "sum"

for (i = 0 ; i < one ; i++) $1 return value, not saved


result = sum(result, two);

return result;
}

frame size = # args (to ‘sum’) + # registers + # locals

= 2 + 3 + 0
= 5
multiply:
subui $sp, $sp, 5

Example Subroutine sw
sw
$4, 2($sp) # result
$5, 3($sp) # i
sw $ra, 4($sp)

int multiply(int one, int two) add $4, $0, $0


{ add $5, $0, $0
loop:
int result = 0, i;
wcc -S lw $1, 5($sp)
slt $1, $5, $1
for (i = 0 ; i < one ; i++) beqz $1, end_loop
result = sum(result, two);
lw $1, 6($sp)
return result; sw $1, 1($sp)
} sw $4, 0($sp)
jal sum
add $4, $0, $1
+0 arg 1 addi $5, $5, 1
Arguments to pass
+1 arg 2 to sum j loop
end_loop:
+2 $4 backup
add $1, $0, $4
+3 $5 backup Saved
+4 $ra backup registers lw $ra, 4($sp)
lw $5, 3($sp)
+5 one lw $4, 2($sp)
Arguments passed
+6 two addui $sp, $sp, 5
in to multiply jr $ra
Summary
• Subroutines are called using jal and return using
jr $ra.
• Stacks are Last In, First Out (LIFO) data structures:
• Push adds an item to the top of the stack.
• Pop removes the item at the top of the stack.
• Subroutines use the stack to:
• Pass parameters.
• Back up and restore register values.
• Store local variables.
• This information is stored in a stack frame.

You might also like