Goto/Gosub a variable and psuedo On Goto/Gosub

Share your advanced PureBasic knowledge/code with the community.
User avatar
DoubleDutch
Addict
Addict
Posts: 3220
Joined: Thu Aug 07, 2003 7:01 pm
Location: United Kingdom
Contact:

Goto/Gosub a variable and psuedo On Goto/Gosub

Post by DoubleDutch »

These routines let you create easy to use jump tables and jump sequences. With them you can jump to a variable, an address held within an array, or a psuedo On Goto/Gosub system with the list af addresses being in the data section.

Code: Select all

Macro On(address,pos=0)
	(PeekL(address+(pos*4)))
EndMacro

Macro GotoAddress(var)
	JmpCallVarAddress=(var)
	!MOV eax,dword [v_JmpCallVarAddress]
	!JMP eax
EndMacro

Macro GosubAddress(var)
	JmpCallVarAddress=(var)
	!MOV eax,dword [v_JmpCallVarAddress]
	!CALL eax
EndMacro

GotoAddress(On(?routines,1))

test1:	Debug("Between")
test2:	Debug("If you don't see between, it worked!")

Dim MyArray(4)
MyArray(0)=?routine0
MyArray(1)=?routine1
MyArray(2)=?routine2
MyArray(3)=?routine3
MyArray(4)=?routine4

For loop=0 To 4
	GosubAddress(MyArray(loop))
Next

For loop=0 To 4
	GosubAddress(On(?sequencetable,loop))
Next

End

routine0:
	Debug("at 0")
Return

routine1:
	Debug("at 1")
Return

routine2:
	Debug("at 2")
Return

routine3:
	Debug("at 3")
Return

routine4:
	Debug("at 4")
Return

DataSection
routines:
	Data.l	?test1,?test2
sequencetable:
	Data.l	?routine0,?routine1,?routine2,?routine3,?routine4
EndDataSection
https://deluxepixel.com <- My Business website
https://reportcomplete.com <- School end of term reports system
jack
Addict
Addict
Posts: 1358
Joined: Fri Apr 25, 2003 11:10 pm

Post by jack »

the Macro GosubAddress is not procedure safe, because of the CALL eax messes with the stack.
User avatar
DoubleDutch
Addict
Addict
Posts: 3220
Joined: Thu Aug 07, 2003 7:01 pm
Location: United Kingdom
Contact:

Post by DoubleDutch »

The return address is pushed on the stack by the call, it get pop'ed off by the return. Why do you think that this messes up the stack?
https://deluxepixel.com <- My Business website
https://reportcomplete.com <- School end of term reports system
jack
Addict
Addict
Posts: 1358
Joined: Fri Apr 25, 2003 11:10 pm

Post by jack »

as long as you use the code outside of procedures you ar OK, but used inde a procedure is asking for trouble, let's say your procedure initializes some variables and then use you macros to go to subroutine that uses the variables, because the CALL pushed the EIP onto the stack, your variables may not point to valid memory.
for the same reason you can't use gosub in a procedure.
User avatar
DoubleDutch
Addict
Addict
Posts: 3220
Joined: Thu Aug 07, 2003 7:01 pm
Location: United Kingdom
Contact:

Post by DoubleDutch »

I think your wrong because the routine will be assembled outside the procedure - when it is assembled it's variable access will not be the procedures stack based variables, but regular program variables. It shouldn't effect the procedure variables at all. I don't think that you could actually access the procedure variables anyhow unless you accessed them directly on the stack?

Here is some code to demo what I mean:

Code: Select all

Global JmpCallVarAddress

Macro On(address,pos=0)
	(PeekL(address+(pos*4)))
EndMacro

Macro GotoAddress(var)
	JmpCallVarAddress=(var)
	!MOV eax,dword [v_JmpCallVarAddress]
	!JMP eax
EndMacro

Macro GosubAddress(var)
	JmpCallVarAddress=(var)
	!MOV eax,dword [v_JmpCallVarAddress]
	!CALL eax
EndMacro

Procedure TestProc()
	blah=20
	GosubAddress(?routine)
	Debug("Blah is: "+Str(blah))
EndProcedure

blah=30

TestProc()
TestProc()

End

routine:
	Debug("Main blah: "+Str(blah))
	Debug("at 1")
Return
I had to add the global at the top, because the JmpCallVarAddress would otherwise be local to the procedure and the actual jmp would get the variable that "belonged" to the main program - so it would go the the last jumped location that the main program used. It was either that or have a local version of the macro that jmp'ed to p.v_JmpCallVarAddress instead of v_JmpCallVarAddress.

I haven't looked at the real gosub - but it will probably work in much the same way?
https://deluxepixel.com <- My Business website
https://reportcomplete.com <- School end of term reports system
User avatar
DoubleDutch
Addict
Addict
Posts: 3220
Joined: Thu Aug 07, 2003 7:01 pm
Location: United Kingdom
Contact:

Post by DoubleDutch »

Here is another test to show you it works fine...

Code: Select all

Global JmpCallVarAddress

Macro On(address,pos=0)
	(PeekL(address+(pos*4)))
EndMacro

Macro GotoAddress(var)
	JmpCallVarAddress=(var)
	!MOV eax,dword [v_JmpCallVarAddress]
	!JMP eax
EndMacro

Macro GosubAddress(var)
	JmpCallVarAddress=(var)
	!MOV eax,dword [v_JmpCallVarAddress]
	!CALL eax
EndMacro

Procedure TestProc()
	blah=20
	GosubAddress(?routine)
	Debug("Blah is: "+Str(blah))
EndProcedure

blah=30

TestProc()
TestProc()

End

routine:
	Debug("Main blah: "+Str(blah))
	Gosub routine2
Return

routine2:
	Gosub routine3
	Gosub routine3
Return

routine3:
	Debug("At routine3!")
Return
https://deluxepixel.com <- My Business website
https://reportcomplete.com <- School end of term reports system
User avatar
DoubleDutch
Addict
Addict
Posts: 3220
Joined: Thu Aug 07, 2003 7:01 pm
Location: United Kingdom
Contact:

Post by DoubleDutch »

Here is a test that gosubs a routine that calls a procedure that gosubs another routine (it also tests strings)... No problems:

Code: Select all

Procedure TestProc()
	string$="test of string"
	blah=20
	GosubAddress(?routine)
	Debug("Blah is: "+Str(blah))
	Debug("string: "+string$)
EndProcedure

Procedure TestProc2()
	blah=40
	GosubAddress(?routine3)
	Debug("Proc2 blah: "+Str(blah))
EndProcedure

blah=30

TestProc()
TestProc()

End

routine:
	Debug("Main blah: "+Str(blah))
	Gosub routine2
Return

routine2:
	TestProc2()
	TestProc2()
Return

routine3:
	Debug("At routine3!")
Return
You need the Macros from the previous post.
https://deluxepixel.com <- My Business website
https://reportcomplete.com <- School end of term reports system
jack
Addict
Addict
Posts: 1358
Joined: Fri Apr 25, 2003 11:10 pm

Post by jack »

DoubleDutch try this then

Code: Select all

Global JmpCallVarAddress

Macro GosubAddress(var) 
   JmpCallVarAddress=(var) 
   !MOV eax,dword [v_JmpCallVarAddress] 
   !CALL eax 
EndMacro 


Procedure test1() 
  NewList gosublist.l() 
  retaddress.l 

  i.l 
  For i=1 To 10 
    GosubAddress(?factorial)
    PrintN(Str(fac)) 
  Next 
  ProcedureReturn 
  factorial: 
    If fac.l=0 
      fac=1 
      n.l=1 
    EndIf 
    fac=fac*n
    n=n+1
  Return

EndProcedure 



  OpenConsole() 
  test1() 
  Input() 
  CloseConsole() 
  End 
and you see why I used an extra return label and did not mess with the stack

Code: Select all

Macro _gosub(label,retrn) 
  AddElement(gosublist()) 
  gosublist()=?retrn 
  GoToEIP(?label)
EndMacro 

Macro _return 
  retaddress=gosublist() 
  DeleteElement(gosublist()) 
  GoToEIP(retaddress)
EndMacro 

Procedure test1() 
  NewList gosublist.l() 
  retaddress.l 
  i.l 
  For i=1 To 14 
    _gosub(factorial,l1) ;you need to supply a return label, in this case l1 
    PrintN("This never gets executed") 
    l1: PrintN(Str(fac)) 
  Next 
  ProcedureReturn 
  factorial: 
    If fac.l=0 
      fac=1 
      n.l=1 
    EndIf 
    fac=fac*n 
    n=n+1 
  _return 

EndProcedure 


  OpenConsole() 
  test1() 
  Input() 
  CloseConsole() 
  End 
User avatar
DoubleDutch
Addict
Addict
Posts: 3220
Joined: Thu Aug 07, 2003 7:01 pm
Location: United Kingdom
Contact:

Post by DoubleDutch »

That is because your routine is still in the procedure and your stack will be messed up. My routine gosubs out of the procedure and back. If you want to gosub to an address within the procedure then you need to make sure that the variables in the gosub are either global or static:

Code: Select all

Global fac,n

Procedure test1()
  i.l
  For i=1 To 10
    GosubAddress(?factorial)
    PrintN(Str(fac))
  Next
  ProcedureReturn
  
  factorial:
    If fac.l=0
      fac=1
      n.l=1
    EndIf
    fac=fac*n
    n=n+1
  Return

EndProcedure
or

Code: Select all

Procedure test1()
	Static fac,n

  i.l
  For i=1 To 10
    GosubAddress(?factorial)
    PrintN(Str(fac))
  Next
  ProcedureReturn
  
  factorial:
    If fac.l=0
      fac=1
      n.l=1
    EndIf
    fac=fac*n
    n=n+1
  Return

EndProcedure
https://deluxepixel.com <- My Business website
https://reportcomplete.com <- School end of term reports system
jack
Addict
Addict
Posts: 1358
Joined: Fri Apr 25, 2003 11:10 pm

Post by jack »

DoubleDutch why do you think gosubs are not allowed inside a procedure?
using your macros inside procedures is not safe, and jumping outside of procedures makes me cringe, but you don't want to believe me go ahead.
User avatar
DoubleDutch
Addict
Addict
Posts: 3220
Joined: Thu Aug 07, 2003 7:01 pm
Location: United Kingdom
Contact:

Post by DoubleDutch »

using your macros inside procedures is not safe
But I've demonstrated (by adding 1 line to your test) that they are.
jumping outside of procedures makes me cringe
:shock: Why does it make you cringe?

I think that Gosubs are not allowed within a procedure to an address within a procedure because if you access a local variable then you will accessing the wrong location and will get either corrupt data (a read) or you will corrupt the stack (a write).

In my tests Gosub'ing out of a procedure and back should not have these side effects at all. Also Gosub'ing to an address within a procedure and not accessing local variables (other types are fine) will have no side effects either.
but you don't want to believe me go ahead.
Do you mean go ahead and jump or go ahead and gosub? :wink:
https://deluxepixel.com <- My Business website
https://reportcomplete.com <- School end of term reports system
mesozorn
Enthusiast
Enthusiast
Posts: 171
Joined: Fri Feb 20, 2009 2:23 am

Post by mesozorn »

In VB, you can easily do:

Code: Select all

Public Sub Test()
a = 20
GoSub Whatever
Exit Sub

Whatever:
MsgBox "Value is " + Str(a)
Return
End Sub
... gosubing and accessing local variables without messing up the stack or causing any problems whatsoever. It should be just as simple to do this in PB.
Post Reply