| Version 1 (modified by rme, 5 years ago) |
|---|
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.
