Hi guys, I recently came across this pattern in the Acorn DFS 0.90 ROM (
http://mdfs.net/Info/Comp/BBC/DFS/DFS090.zip), and wondered whether any of you have come across or used something like it before, and what you all thought of it.
The idea is to create a routine that you can "JSR" into at the start of one of your own routines, and have it save all the registers and arrange for them to be restored automatically when your routine returns, without your routine having to explicitly pull them from the stack.
I initially thought the purpose was to save code size, as I guess it reduces 10 bytes of prologue/epilogue to just 3 bytes of prologue, and also allows tail calls at the end of routines as there's no longer any epilogue needed. But I'm not sure, because that's not a huge amount of space saving. It also strikes me that this is a kind of continuation, and potentially knowing the exact stack shape might allow it to arrange a kind of deferred return later on, e.g. after a latent operation has finished.
I'm not convinced though, so if anyone has any better ideas of the value, do let me know!
The code below is a reformatting of the disassembled code, with my own label names and comments to try to explain what's going on.
In a nutshell though, on entry we have a return address on the stack as usual - which is usually right at the start of one of the ROM's API entry points. And above that is the return address of the caller of that routine. What this code does is first push all the registers to save their values, and then it shuffles the stack frame around so that there are two return frames on the stack. The deepest frame restores the registers and returns to the caller's caller; and the shallow one restores the registers and returns to the caller itself. Between the two is inserted the address of the new epilogue code.
So after rearranging the stack, this fragment's epilogue runs, restoring the registers and returning to the immediate caller; but leaving this epilogue's own address on the stack. So when the caller returns, now it runs this epilogue again, which restores the register from the deeper stack frame, and returns to the caller's caller.
Code:
saveregs:
; stack contents:
; ...
; callercaller_hi
; callercaller_lo
; caller_hi
; caller_lo
PHA ; A
TXA
PHA ; X
TYA
PHA ; Y
LDA #>(restoreregs-1)
PHA ; restoreregs_hi
LDA #<(restoreregs-1)
PHA ; restoreregs_lo
; duplicate A,X,Y and the caller address
LDY #&05
loop1:
TSX
LDA &0107,X
PHA
DEY
BNE loop1
; now also another copy each of:
; caller_hi
; caller_lo
; A
; X
; Y
; copy A,X,Y,restore_hi,restore_lo,caller_hi,caller_lo,A,X,Y up two positions,
; overwriting top copy of caller_hi, caller_lo, and leaving extra copies
; of X, Y still pushed at the end
LDY #&0A
loop2:
LDA &0109,X
STA &010B,X
DEX
DEY
BNE loop2
PLA ; discard extra copies of X and Y at the end of the stack
PLA
; When we fall through here, it restores Y,X,A and returns to the caller
; When this gets called a second time via the caller's RTS, it restores Y,X,A and returns to the caller's caller
restoreregs:
PLA
TAY
PLA
TAX
PLA
RTS