wiki:Internals/StackFrames

Version 2 (modified by gb, 6 years ago) (diff)

some minor changes: show register args set in call, just leave/ret to return.

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>)
(movl <4th arg> (% arg_y))
(movl <5th arg> (% arg_z))
(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

There are some cases where a function might or might not have received any args on the stack and has to decide at runtime how to build its stack frame.

Returning a single value is a matter of leave/ret, regardless of how the frame was constructed.