Ben Bacarisse said:
I think you are probably confusing yourself by writing []s and calling
the contents a stack. I am still not sure what you want, but the new
words "runStack" and "framePointers" and the function call example put
this in a context I understand.
Why do think a new stack is needed? The conventional thing to do is to
push the function's arguments and the record the new top of stack in the
frame pointer. Since the previous frame pointer will need to be
restored when this function exits, it is reasonable to record these
frame pointers in a stack, though some implementations will use the main
stack for these as well (often just relying on the register save/restore
mechanism). Anyway, that aside, the effect is that the 3 won't be on a
new stack, just in a portion of the main stack identified by the frame
pointer for this function call.
If function G calls function F we get a stack that looks like this at
the point that F starts to run:
][args for G | G's locals][args for F |
Now the []s don't denote stacks. Each bracketed part is a stack
frame -- a region on the main stack. The |s mark the frame pointers.
Function arguments are to the left, and locals are to the right.
To be very explicit, let's assume that your stack uses plain integer
indexes and the functions look like this
function G(a) { c = 42; F(c, a) }
function F(a, b) { ... don't care about what's in F }
and G is called like this, G(99). If 66 stack cells have already been
used, we get the following situation:
index ... 67 68 69 70 71
content ... 99 42 99 42
corresponding to ... G:a G:C F:b F:a
frame pointer stack: ... 68 71
Top of stack: 71.
The two numbers in the frame pointer stack mark the |s in the previous
schematic. The code is now ready to allocate the first local variables
in F (if any).
All this excludes any mention of the return address. Maybe that's being
handled separately.
Well, I don't know of any processor that uses a separate stack for the
frame pointers. See for example the instructions LINK and UNLK of the
680x0 (there are similar instructions on X86 and others). The top frame
pointer is usually kept in A6, while the stack pointer is A7=SP.
At the entry point of functions, there's an instruction:
LINK A6,#-localSpace
and at the exit of them, there are:
UNLK A6
RTN
So for functions like:
function G(a) { c = 42; F(c, a) }
function F(a, b) { ... don't care about what's in F }
and starting with A7=SP=0xfff0 (stacks tend to grow downward in
processors, but it makes no differences, only the offsets are
opposites):
A7: 0xfff0
A6: 0xfff8
the caller pushes 99 for the argument of G, then calls G, with a JSR G,
which pushes the return address onto the stack:
0xfff0:
0xffec: 99
0xffe8: return-address-to-caller
A7: 0xffe8
A6: 0xfff8
then G executes LINK A6,#-4 since it needs one local variable.
0xfff0:
0xffec: 99
0xffe8: return-address-to-caller
0xffe4: 0xfff8 ; old fram pointer
0xffe0: random value for c
A7: 0xffe0
A6: 0xffe4 ; G frame pointer
c:=42 This writes into the local frame at the address -4(A6):
0xfff0:
0xffec: 99
0xffe8: return-address-to-caller
0xffe4: 0xfff8 ; old fram pointer
0xffe0: 42 ; c
A7: 0xffe0
A6: 0xffe4 ; G frame pointer
F(c,a) this reads the local frame: c is in the local variables at
-4(A6), and a is in the parameters at 8(A6). The arguments are pushed
on the stack:
0xfff0:
0xffec: 99
0xffe8: return-address-to-caller
0xffe4: 0xfff8 ; old fram pointer
0xffe0: 42 ; c
0xffdc: 42
0xffd8: 99
A7: 0xffd8
A6: 0xffe4 ; G frame pointer
then F is called.
0xfff0:
0xffec: 99
0xffe8: return-address-to-caller
0xffe4: 0xfff8 ; old fram pointer
0xffe0: 42 ; c
0xffdc: 42
0xffd8: 99
0xffd4: return address into G
A7: 0xffd4
A6: 0xffe4 ; G frame pointer
So F executes LINK A6,#-n (n depending on the local storage F needs):
And so on. When F returns, it calls:
UNLK A6
which restores the stack to:\
0xfff0:
0xffec: 99
0xffe8: return-address-to-caller
0xffe4: 0xfff8 ; old fram pointer
0xffe0: 42 ; c
0xffdc: 42
0xffd8: 99
0xffd4: return address into G
A7: 0xffd4
A6: 0xffe4 ; G frame pointer
and:
RTN
which returns to G
0xfff0:
0xffec: 99
0xffe8: return-address-to-caller
0xffe4: 0xfff8 ; old fram pointer
0xffe0: 42 ; c
0xffdc: 42
0xffd8: 99
A7: 0xffd8
A6: 0xffe4 ; G frame pointer
When G returns, it executes:
UNLK A6
which restores the stack to:
0xfff0:
0xffec: 99
0xffe8: return-address-to-caller
A7: 0xffe8
A6: 0xfff8
and then:
RTN
and we're back to the caller:
0xfff0:
0xffec: 99
A7: 0xffec
A6: 0xfff8