Gosub from inside Procedures...sometimes very useful.

Got an idea for enhancing PureBasic? New command(s) you'd like to see?
User avatar
fsw
Addict
Addict
Posts: 1603
Joined: Tue Apr 29, 2003 9:18 pm
Location: North by Northwest

Post by fsw »

Hey Psychophanta your code works also fine with global variables:

Code: Select all

Procedure.l go_sub(d, f, g)
Shared a,b,c
  If a = 1
    !jmp near lbl_a1;<-a Gosub here write next address (rett) into  at compilation time and then just jumps.
    !rett:;                                                       |
  EndIf;                                                         |
  ;                                                              /
  Debug "a="+Str(a)+", b="+Str(b)+", c="+Str(c) ;               |
  ProcedureReturn ;                                           /
;                                                              /
!lbl_a1:;                                                     /
;                                                            /
  a = b+c;                                                  /
!jmp near rett;<-------------------------------------------/
EndProcedure

a=1:b=1:c=1
go_sub(2, 2, 2) 

8)
Pupil
Enthusiast
Enthusiast
Posts: 715
Joined: Fri Apr 25, 2003 3:56 pm

Post by Pupil »

Psychophanta wrote: Well, fsw, Pupil and Fred, please, tell me if i am crazy, or on the contrary, it should be possible to implement a Gosub for procedures in this way:

Code: Select all

Procedure.l go_sub(a, b, c) 
  If a = 1
    !jmp near lbl_a1;<-a Gosub here write next address (rett) into  at compilation time and then just jumps.
    !rett:;                                                       |
  EndIf;                                                         |
  ;                                                              /
  Debug "a="+Str(a)+", b="+Str(b)+", c="+Str(c) ;               |
  ProcedureReturn ;                                           /
;                                                              /
!lbl_a1:;                                                     /
;                                                            /
  a = b+c;                                                  /
!jmp near rett;<-------------------------------------------/ 
EndProcedure 

go_sub(1, 1, 1) 
This is just the pupil example, but only changed CALL, and RET by JMP.

NOTE: Of course the PB "Return" instruction should be replaced by a jmp too. As is shown in the example.
Replacing 'Gosub' with jmp would work, the big "IF" with that solution is if you use gosub to the same label from several places in the procedure, then you would have to create identical subroutines for each 'Gosub' -well not totaly identical they would need to jump back to different places...
Fred
Administrator
Administrator
Posts: 18162
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Post by Fred »

May be another (private) stack could be used this special case... Would makes the code slower/bloated but at least it works. Another solution is to use a register to handle relative access to local variable ('ebp' like in C). So all the trick to gain one register will be gone... :?
User avatar
Psychophanta
Always Here
Always Here
Posts: 5153
Joined: Wed Jun 11, 2003 9:33 pm
Location: Anare
Contact:

Post by Psychophanta »

May be another (private) stack could be used this special case... Would makes the code slower/bloated but at least it works. Another solution is to use a register to handle relative access to local variable ('ebp' like in C). So all the trick to gain one register will be gone...
No, Fred, i have though about, but might be preferrable to leave it like now, i think.

Mmmh, Pupil explanation is very true:
Replacing 'Gosub' with jmp would work, the big "IF" with that solution is if you use gosub to the same label from several places in the procedure, then you would have to create identical subroutines for each 'Gosub' -well not totaly identical they would need to jump back to different places...
I didn't thought about this, which is just the "raison d'être" of subroutines (to be called from several places in code). :x :(
User avatar
Psychophanta
Always Here
Always Here
Posts: 5153
Joined: Wed Jun 11, 2003 9:33 pm
Location: Anare
Contact:

Post by Psychophanta »

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)
User avatar
newtheogott
Enthusiast
Enthusiast
Posts: 120
Joined: Sat Apr 26, 2003 2:52 pm
Location: Germany, Karlsruhe
Contact:

Re: Gosub from inside Procedures...sometimes very useful.

Post by newtheogott »

Gosub, For .. NEXT ... If .. ELSe.
Such constructs are mostly Stacks,
For example, if you open a "For", you put the address of this "For" on the stack, so the next "Next" when its found knows where it belongs too.
With Gosub its just the same.
If you can not use this stack because its needed by variables, you can always use another "Gosub stack" somewhere else.
There are many ways this could be done. Fred pointed out some of them.
In fact a GOSUB will not have the large speed advantage this way, but it could be useful to structure Subroutines.
People coming from other BASIC systems would find it useful and easier if this limitation will fall one day.
--Theo Gottwald
-----------------------------------------
http://www.it-berater.org * http://www.fa2.de * http://www.smart-package.com
Post Reply