What about to finish each subroutine with "jmp" instruction (could be a relative "jmp" short instruction format; E9 OpCode) and write there (
at execution time) the return address depending on the place where the routine has been called?
I mean writing there (
in execution time, instead in compile time) the return address each time a Gosub is reached (should be a must to treat that address like a hidden variable).
This method would allow "infinite" nested GOSUBs without problem; besides of avoiding the creation of dedicated stack for each procedure, and besides, it will be even faster than assembler CALL and RET instructions, so, if i was Fred i would use it for all Gosub-Return.
The only thing to keep in mind is to leave the last DWord or 48-bit as read/write space. As if it was a hidden variable located just after "jmp" opcode.
Here is what i mean, i have had to make use of Rings idea to selfmodify code space in execution time:
Code: Select all
Procedure.l go_sub(a,b,c)
If a=1
;Gosub routine1
!mov dword[l_routine1_ret-4],ret1-l_routine1_ret
!jmp l_routine1
!ret1:
EndIf
If a=3
;Gosub routine1 ;again
!mov dword[l_routine1_ret-4],ret2-l_routine1_ret
!jmp l_routine1
!ret2:
EndIf
Debug "a="+Str(a)+", b="+Str(b)+", c="+Str(c)
ProcedureReturn
; --------SubRoutines:
routine1:
;Gosub NestedRoutine
!mov dword[l_routine2_ret-4],NestRet-l_routine2_ret
!jmp l_nestedroutine
!NestRet:
a=b+c
;Return:
!db $E9,0,0,0,0;<-jmp OpCode and a dword space
routine1_ret:
NestedRoutine:
b+c
;Return:
!db $E9,0,0,0,0;<-jmp OpCode and a dword space
routine2_ret:
EndProcedure
#PAGE_READWRITE=4
VirtualProtect_(?routine1,?routine2_ret-?routine1,#PAGE_READWRITE,@OrigMode.l)
go_sub(1,1,1)
VirtualProtect_(?routine1,?routine2_ret-?routine1,OrigMode.l,@Mode)
FlushInstructionCache_(GetCurrentProcess_(),?routine1,?routine2_ret-?routine1)