Local Variables
Local storage refers to variables local to the function currently executing. This is in contrast to global variables.
Using stack frames and allocating variables on the stack is a common practice. WOPR is little different in that stack frames are supported directly by the CPU, while commonly stack frames are implemented in higher level languages.
This choice was made to make WOPR programming a little easier and to make WOPR code more compact, especially since it has only 64K of memory.
Until now, return stack contained only return address, address of the instruction where to continue execution when the call returns.
Now, we introduce proper stack frame. Each call creates a stack frame, and each return removes it.
Return Stack | Size |
---|---|
return address | word |
stack frame size | word |
… | variable |
In the simplest case, if the function does not have any local variables, the
stack frame will contain only return address
and stack frame size
.
Return Stack | Size | Offset | Value |
---|---|---|---|
return address | word | -2 | ? |
stack frame size | word | 0 | 0 |
If the function has a single variable which is a single byte, stack frame would look like this:
Return Stack | Size | Offset | Value |
---|---|---|---|
return address | word | -2 | ? |
stack frame size | word | 0 | 2 |
variable 1 | byte | 2 | ? |
In a more complex example with multiple variables, two word variables a and one byte sized one.
Return Stack | Size | Offset | Value |
---|---|---|---|
return address | word | -2 | ? |
stack frame size | word | 0 | 5 |
variable 1 | word | 2 | ? |
variable 2 | word | 4 | ? |
variable 3 | byte | 5 | ? |
Stack frame is laid out so that the stack frame size
is pointed to by the RS
register. So we say that stack frame size
is at offset 0.
First variable is at offset 2, and location of each subsequent one is calculated by adding the size of each variable.
Function must allocate the space for any local variables it will use. This is
done using alloc
instruction.
The most common case is to allocate space for all variables using a single instruction. So a function in our previous example, which allocates five bytes for two word variables and one byte variable, would look like this:
my_func:
push 5
alloc
; ... do your thing here
ret ; this will return and remove stack frame including all variables
alloc
can be executed multiple times, and each one would increase the size of
the stack frame and thus allocating additional memory for your variables.
my_func:
push 5
alloc
push 10
alloc
ret ; this will return and remove stack frame including all variables
Above example would create a stack frame with 15 bytes allocated to local variables, like so:
Return Stack | Size | Offset | Value |
---|---|---|---|
return address | word | -2 | ? |
stack frame size | word | 0 | 15 |
variables | 15 bytes | 2-15 | ? |
This pattern is useful when the size of some variable, usually a buffer, is not known ahead of time and has to be calculated.
free
does the opposite of alloc
and would reduce the size of the stack
frame.
my_func:
push 8
alloc
push 4
free
; at this point, we have only 4 bytes allocated
ret ; this will return and remove stack frame including all variables
Like mentioned earlier, the stack frame is automatically released when
a function executes ret
or iret
. This will automatically free the space for
local variables. So in most cases free
is not required. It is available
nevertheless to allow implementing complex functions.
The easiest way to access local variables is using the lpush
instruction. It
is very similar to push
except the value pushed is first added to RS, so the
value pushed is RS+P0
. This effectively pushes the absolute address of
a variable so that it can be accessed using load
or sto
instructions.
my_func:
push 4
alloc ; allocate 2 word variables
; initialize first variable (offset 2) to zero
lpush 2
push 0
sto
; initialize second variable (offset 4) to zero
lpush 4
push 0
sto
ret ; this will return and remove stack frame including all variables