While I was implementing the 'loop' construct for my experimental
language (see these blog posts

http://subvert-the-dominant-paradigm.net/blog/?p=28
http://subvert-the-dominant-paradigm.net/blog/?p=29

) I realised that I had to implement non local return functionality.

At first I thought that I could just grab the frame pointer at runtime
and pop off as many stack frames as needed, resuming at the target
frame.

Unfortunately this is not possible because the calling convention
requires that the 'callee saved registers' (ebx, esi and edi on x86) be
restored by the callees before they return.

So what I did was to modify the code generation process so that after
generating code for a function, it generated an extra "cleanup" sequence
which restored these callee saved registers and popped the stack frame.

The cleanup pseudo-instruction:

         (:void (:cleanup-and-jump) ,#'(lambda (op cg)
                                      (emit-epilogue cg)
                                      (jmp-reg cg (edx cg))))

The code might look like this:

00000018 <main_cleanup>:
  18:   5b                      pop    %ebx
  19:   5d                      pop    %ebp
  1a:   ff e2                   jmp    *%edx

Here it is popping %ebx (because ebx was used by this function) and
restoring ebp.

These cleanup routines always exit by jumping through the edx register.

In this example control is returned from block c to block a

(define |main| (lambda ()
                 (block a
                    (let ()
                        (block b
                         (let ()
                           (block c
                            (let ()
                             (return-from a 99))))))
                    (call-c |printf| "should not be printed
"))))

To do so, the return-from form is compiled into a sequence of jumps to
the cleanup routines for block c and block b.

Before each jump we set edx to point to the next instruction, so that we
can execute a sequence of cleanups before returning. (This is done by
adding the address of the current function to the offset which we need
to return to)

(:void (:setup-return-jump) ,#'(lambda (op cg)
  (movl-label cg (destination op) :reg edx cg))
  (movl-imm cg (arg (lambda-address op)) :reg (ecx cg))
  (addl-reg cg (ecx cg) :reg (edx cg))))

This becomes something like:

  71:   b8 63 00 00 00          mov    $0x63,%eax ; the return value
  76:   89 c0                   mov    %eax,%eax
  78:   50                      push   %eax ; we save the return value
while cleanups are executed
  79:   ba 28 00 00 00          mov    $0x28,%edx
  7e:   b9 00 00 00 00          mov    $0x0,%ecx
                        7f: R_386_32    _block_c
  83:   01 ca                   add    %ecx,%edx ; save the return
address to continue execution

; for some reason I could not figure out how to do a direct far jump on
x86, so instead I do an indirect jump through this
'STATE-CLEANUP-ADDRESS' global

  85:   b8 00 00 00 00          mov    $0x0,%eax
                        86: R_386_32    _block_c_cleanup
  8a:   b9 00 00 00 00          mov    $0x0,%ecx
                        8b: R_386_32    STATE-CLEANUP-ADDRESS
  8f:   89 01                   mov    %eax,(%ecx)
  91:   58                      pop    %eax
  93:   ff 25 00 00 00 00       jmp    *0x0
                        95: R_386_32    STATE-CLEANUP-ADDRESS

At this point I have only needed this for implementing "return-from",
but in the future I hope that I will be able to implement exceptions
using the same technique. Any comments on alternative ways to do this? 

I also thought about storing external information about each function
somewhere (describing the stack size and which callee saved registers
needed to be restored when unwinding the stack). Another idea was to
identify call sites which could be returned to by a non-local-return,
and force the caller to save the callee saved registers. This wouldn't
work with the current linking model though.

John





_______________________________________________
fonc mailing list
[email protected]
http://vpri.org/mailman/listinfo/fonc

Reply via email to