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
