Page 2 of 2

Posted: Wed Nov 26, 2003 9:49 pm
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)

Posted: Wed Nov 26, 2003 11:40 pm
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...

Posted: Thu Nov 27, 2003 12:08 am
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... :?

Posted: Thu Nov 27, 2003 10:07 am
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 :(

Posted: Thu Nov 27, 2003 1:03 pm
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)

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

Posted: Sat Mar 05, 2016 8:26 am
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.