wiki:Internals/StackFrames

Version 1 (modified by rme, 7 years ago) (diff)

elementary notes on stack frames

Stack frames on x86 ports

When all of a function's arguments fit into argument registers, building a stack frame is very simple (cf. vinsn save-lisp-context-no-stack-args):

(push (% ebp))
(mov (% esp) (% ebp))

After this, the stack looks like:

4(%ebp) tagged return address
0(%ebp) saved ebp
-4(%ebp) locals
.
.
.
0(%esp) top of stack

When some arguments must be pushed, it's a little more complicated. The caller first needs to reserve space for a stack frame, and then push any args. Say there's a function FOO that takes five args. In FOO's caller, we see something like:

(push ($ reserved-frame-marker))   ;could be NIL or 0 or some other GC-safe thing
(push ($ reserved-frame-marker))
(push <1st arg>)
(push <2nd arg>)
(push <3rd arg>)
(set-nargs 5)
(movl (@ 'FOO (% fn)) (% temp0))
(:talign 5)  ;align return address
(call (@ symbol.fcell (% temp0)))

Thus, on entry to FOO, the stack looks like this:

+ 20 reserved-frame-marker
+ 16 reserved-frame-marker
+ 12 arg1
+ 8 arg2
+ 4 arg3
0(%esp) tagged return address

We now have to do a bit of shuffling to put the saved frame pointer and the return address into the reserved places on the stack. The vinsn save-lisp-context-variable-arg-count does that with code like this:

(movl (% ebp) (@ 16 (% esp))  ;save old frame pointer
(leal (@ 16 (% esp)) (% ebp)) ;set new frame pointer
(popl (@ 4 (% ebp))           ;relocate return address

Now, the stack looks like this:

4(%ebp) return address
0(%ebp) saved ebp
-4(%ebp) 1st stack arg
-8(%ebp 2nd stack arg
-12(%ebp) 3rd stack arg

Returning is then a matter of popping the pushed args, and leave/ret.