MACRO: variable evaluation ?

Just starting out? Need help? Post your questions and find answers here.
User avatar
charvista
Addict
Addict
Posts: 949
Joined: Tue Sep 23, 2008 11:38 pm
Location: Belgium

Re: MACRO: variable evaluation ?

Post by charvista »

Fascinating...
This is really phantasmagorical Danilo, it is the PureBasic's paradise... :D
Danilo, thank you very much!!!
I am sure that our other PB-friends here will appreciate your code as well.
I like the use of Call() and Sub()+EndSub, because they are very like native commands, thus professional.
Tested on Win7 64-bit, it works fine.

If I understand well from your code:
1)-- Sub can be used within procedure as well outside procedure, thus anywhere.
2)-- Sub names can be only used once. Good to know, and that's the same rule as labels. OK, this is not a problem.
3)-- ExitSub works similarly to Break. (Excellent!)
4)-- Sub's do not need to be declared, they can be anywhere, before or after the caller.

Please correct me if I am wrong.

5)-- Sub's cannot be nested. This is perhaps the only point which is a pity. It is rare that a sub calls itself (but it occurs), however, it is very frequent that a sub calls another sub. If if was possible to define a different global pointer for each sub that is called, then I think that any number of nesting would be possible?
Obviously, any sub that is entered should be exitted in the same order.
- Windows 11 Home 64-bit
- PureBasic 6.10 LTS (x64)
- 64 Gb RAM
- 13th Gen Intel(R) Core(TM) i9-13900K 3.00 GHz
- 5K monitor with DPI @ 200%
User avatar
Danilo
Addict
Addict
Posts: 3036
Joined: Sat Apr 26, 2003 8:26 am
Location: Planet Earth

Re: MACRO: variable evaluation ?

Post by Danilo »

charvista wrote:If I understand well from your code:
1)-- Sub can be used within procedure as well outside procedure, thus anywhere.
2)-- Sub names can be only used once. Good to know, and that's the same rule as labels. OK, this is not a problem.
3)-- ExitSub works similarly to Break. (Excellent!)
4)-- Sub's do not need to be declared, they can be anywhere, before or after the caller.

Please correct me if I am wrong.
That's all correct.
charvista wrote:5)-- Sub's cannot be nested. This is perhaps the only point which is a pity. It is rare that a sub calls itself (but it occurs), however, it is very frequent that a sub calls another sub. If if was possible to define a different global pointer for each sub that is called, then I think that any number of nesting would be possible?
Obviously, any sub that is entered should be exitted in the same order.
I added a call stack now, so Subs can call Subs that call Subs. :D

At the start of the include you find: #SUB_STACKSIZE = 100000
It means you can call Subs nested with 100,000 steps deep.
If you need more, change #SUB_STACKSIZE to how many million nested calls you need.

We speak about nested calling here. You can not nest the Subs in code.
This is not allowed:

Code: Select all

Sub(s1)
   Sub(s2)
   EndSub
EndSub
You have to write the Subs one after another. It is nested calling that is possible:

Code: Select all

Sub(Nested_1)
    Call(Nested_2)
EndSub

Sub(Nested_2)
    Call(Nested_3)
EndSub

Sub(Nested_3)
EndSub
Nested calling works, but please note: It is not thread safe yet!
The Sub call stack is global now, so different threads would use the same stack and things would get messed up.

But now the code with call stack: ;)

Code: Select all

;--------------------------------------------------------------------------------------------------------
;
; START INCLUDE "Sub.pbi"
;
;--------------------------------------------------------------------------------------------------------

#SUB_STACKSIZE = 100000

;--------------------------------------------------------------------------------------------------------

Global __Sub_ReturnAddress__.s{(#SUB_STACKSIZE+1)*(SizeOf(Integer)/SizeOf(character))}
Global __Sub_ReturnIndex.i

Macro Sub(_name_)
    CompilerIf #PB_Compiler_Processor = #PB_Processor_x86 Or #PB_Compiler_Processor = #PB_Processor_x64
        !EndSub_Name equ __EndSub_#_name_
        !  JMP EndSub_Name
        !_sub_#_name_:
    CompilerElse
        CompilerError "Macro Sub(): unsupported processor"
    CompilerEndIf
EndMacro

Macro EndSub
    CompilerSelect #PB_Compiler_Processor
        CompilerCase #PB_Processor_x86
            !  MOV dword EAX, v___Sub_ReturnAddress__
            !  MOV dword EDX, [v___Sub_ReturnIndex]
            !  JMP dword [EAX+EDX*4]
            !EndSub_Name:
        CompilerCase #PB_Processor_x64
            !  MOV RAX, v___Sub_ReturnAddress__
            !  MOV R8, [v___Sub_ReturnIndex]
            !  JMP qword [RAX+R8*8]
            !EndSub_Name:
        CompilerDefault
            CompilerError "Macro EndSub: unsupported processor"
    CompilerEndSelect
EndMacro

Macro ExitSub
    CompilerSelect #PB_Compiler_Processor
        CompilerCase #PB_Processor_x86
            !  MOV dword EAX, v___Sub_ReturnAddress__
            !  MOV dword EDX, [v___Sub_ReturnIndex]
            !  JMP dword [EAX+EDX*4]
        CompilerCase #PB_Processor_x64
            !  MOV RAX, v___Sub_ReturnAddress__
            !  MOV R8, [v___Sub_ReturnIndex]
            !  JMP qword [RAX+R8*8]
        CompilerDefault
            CompilerError "Macro ExitSub: unsupported processor"
    CompilerEndSelect
EndMacro

Macro Call(_name_)
    CompilerSelect #PB_Compiler_Processor
        CompilerCase #PB_Processor_x86
            !  INC dword [v___Sub_ReturnIndex]
            !  MOV dword EAX, v___Sub_ReturnAddress__
            !  MOV dword EDX, [v___Sub_ReturnIndex]
            !  MOV dword [EAX+EDX*4], @f
            !  JMP _sub_#_name_
            !@@:
            !  DEC dword [v___Sub_ReturnIndex]
        CompilerCase #PB_Processor_x64
            !  INC qword [v___Sub_ReturnIndex]
            !  MOV R9, @f
            !  MOV RAX, v___Sub_ReturnAddress__
            !  MOV R8, [v___Sub_ReturnIndex]
            !  MOV qword [RAX+R8*8],R9
            !  JMP _sub_#_name_
            !@@:
            !  DEC qword [v___Sub_ReturnIndex]
        CompilerDefault
            CompilerError "Macro Call(): unsupported processor"
    CompilerEndSelect
EndMacro


;--------------------------------------------------------------------------------------------------------
;
; END INCLUDE "Sub.pbi"
;
;--------------------------------------------------------------------------------------------------------




;XIncludeFile "Sub.pbi"

Procedure RandomAverage()

    For i=1 To 100000
        r=Random(2000-1)
        Call(VerifyR)
    Next

    Debug Str(H)+" times more than half"
    Debug Str(L)+" times less than half"
    

    ProcedureReturn; Quit Proc()
    
    
    Sub(VerifyR)
        If r>1000-1
            H+1
        Else
            L+1
        EndIf
    EndSub
    
EndProcedure


Procedure testthis()
    
    Call(one)
    Call(two)

    ProcedureReturn   ; no problem if you forget it

    Sub(one)
        Debug 1
    EndSub

    Debug "Should never get here, but it is no problem if you forget ProcedureReturn"

    Sub(two)
        Debug 2
    EndSub

EndProcedure


Procedure testthis2(arg1)

    Sub(testthis2_one)
        Debug "In Sub testthis2_one"
        For i = 1 To 2
            Debug "Arg is: "+Str(arg1)
            If arg1 = -1
                Debug "ExitSub"
                ExitSub
                Debug "after ExitSub"
            EndIf
        Next
    EndSub
    
    ;Sub(one)
        ; not allowed, Sub(one) already defined
    ;EndSub
    
    Call(testthis2_one)

EndProcedure


RandomAverage()

Debug "----------------------"

testthis()
Debug 3

Debug "----------------------"

testthis2(123)

Debug "----------------------"

testthis2(-1)

Debug "----------------------"

a = 12

Call(globalsub_2)

Debug "----------------------"

Call(Nested_1)

Debug "OK"


Sub(globalsub_1)
    Debug "In Sub globalsub_1"
    Debug a
EndSub

Sub(globalsub_2)
    Debug "In Sub globalsub_2"
    Debug a
    Call(globalsub_1)
EndSub



Sub(Nested_1)
    Debug "In Sub Nested_1"
    Call(Nested_2)
    Debug "Back in Sub Nested_1"
EndSub

Sub(Nested_2)
    Debug "In Sub Nested_2"
    Call(Nested_3)
    Debug "Back in Sub Nested_2"
EndSub

Sub(Nested_3)
    Debug "In Sub Nested_3"
EndSub
BTW: I updated also the first code (single call Subs).
User avatar
charvista
Addict
Addict
Posts: 949
Joined: Tue Sep 23, 2008 11:38 pm
Location: Belgium

Re: MACRO: variable evaluation ?

Post by charvista »

Danilo,
This is really great! :D
It seems to work fine, until now I have not discovered any problem. But it is just the begin of a new command, so it is still in Beta version! :wink:
I think that, what you just accomplished is of great value of extension to the PureBasic language. The Gosub (renamed as Call) was something that was missing.
I believe that you are now trying to make it threadsafe?
When you think Sub.pbi is complete, I suggest that you put it in the 'Tips & Trics' section with a logical title... or/and I suggest Fred/Freak to consider making it native commands. (Problem is that they need the same for Linux and Mac first.)
Anyway, I cannot thank you enough for this magnificent piece of code, Danilo. It's 5 stars worth. :D
- Windows 11 Home 64-bit
- PureBasic 6.10 LTS (x64)
- 64 Gb RAM
- 13th Gen Intel(R) Core(TM) i9-13900K 3.00 GHz
- 5K monitor with DPI @ 200%
User avatar
Danilo
Addict
Addict
Posts: 3036
Joined: Sat Apr 26, 2003 8:26 am
Location: Planet Earth

Re: MACRO: variable evaluation ?

Post by Danilo »

charvista wrote:I believe that you are now trying to make it threadsafe?
You want everything, huh? ;)

OK, here the thread-safe version. Windows only because it uses some WinAPI calls.

The other versions do not use WinAPI, so it should run on Linux and MacOS as well.
Maybe some tuning required to work with NASM, but the general way should work.

Code: Select all

;--------------------------------------------------------------------------------------------------------
;
; START INCLUDE "Sub_ThreadSafe.pbi"
;
;--------------------------------------------------------------------------------------------------------

#SUB_STACKSIZE = 100000

;--------------------------------------------------------------------------------------------------------

Global __Sub_ThreadLocalStorage.l = TlsAlloc_()

If __Sub_ThreadLocalStorage = #TLS_OUT_OF_INDEXES
    ; FatalError(#ThreadLocalStorage_Alloc_failed) ; shouldn't happen
EndIf


Procedure __Sub_Allocate_ThreadLocalStorage()
    Protected mem.i = AllocateMemory(#SUB_STACKSIZE*SizeOf(Integer))
    If Not mem
        ; FatalError(#Out_of_Memory)
    EndIf
    ;Debug "Allocating new ThreadLocalStorage for Sub/EndSub Call()"
    TlsSetValue_(__Sub_ThreadLocalStorage,mem)
    ProcedureReturn mem
EndProcedure

Macro Sub(_name_)
    CompilerIf #PB_Compiler_Processor = #PB_Processor_x86 Or #PB_Compiler_Processor = #PB_Processor_x64
        !EndSub_Name equ __EndSub_#_name_
        !  JMP EndSub_Name
        !_sub_#_name_:
    CompilerElse
        CompilerError "Macro Sub(): unsupported processor"
    CompilerEndIf
EndMacro

Macro EndSub
    CompilerSelect #PB_Compiler_Processor
        CompilerCase #PB_Processor_x86
            TlsGetValue_(__Sub_ThreadLocalStorage)
            !  MOV dword EDX, [EAX]
            !  JMP dword [EAX+EDX*4]
            !EndSub_Name:
        CompilerCase #PB_Processor_x64
            TlsGetValue_(__Sub_ThreadLocalStorage)
            !  MOV R8, qword [RAX]
            !  JMP qword [RAX+R8*8]
            !EndSub_Name:
        CompilerDefault
            CompilerError "Macro EndSub: unsupported processor"
    CompilerEndSelect
EndMacro

Macro ExitSub
    CompilerSelect #PB_Compiler_Processor
        CompilerCase #PB_Processor_x86
            TlsGetValue_(__Sub_ThreadLocalStorage)
            !  MOV dword EDX, [EAX]
            !  JMP dword [EAX+EDX*4]
        CompilerCase #PB_Processor_x64
            TlsGetValue_(__Sub_ThreadLocalStorage)
            !  MOV R8, qword [RAX]
            !  JMP qword [RAX+R8*8]
        CompilerDefault
            CompilerError "Macro ExitSub: unsupported processor"
    CompilerEndSelect
EndMacro

Macro Call(_name_)
    CompilerSelect #PB_Compiler_Processor
        CompilerCase #PB_Processor_x86
            TlsGetValue_(__Sub_ThreadLocalStorage)
            !  OR  dword EAX, EAX
            !  JNZ @f
            __Sub_Allocate_ThreadLocalStorage()
            !@@:
            !  INC dword [EAX]
            !  MOV dword EDX, [EAX]
            !  MOV dword [EAX+EDX*4], @f
            !  JMP _sub_#_name_
            !@@:
            TlsGetValue_(__Sub_ThreadLocalStorage)
            !  DEC dword [EAX]
        CompilerCase #PB_Processor_x64
            TlsGetValue_(__Sub_ThreadLocalStorage)
            !  OR  qword RAX, RAX
            !  JNZ @f
            __Sub_Allocate_ThreadLocalStorage()
            !@@:
            !  INC qword [RAX]
            !  MOV R9, @f
            !  MOV R8, [RAX]
            !  MOV qword [RAX+R8*8],R9
            !  JMP _sub_#_name_
            !@@:
            TlsGetValue_(__Sub_ThreadLocalStorage)
            !  DEC qword [RAX]
        CompilerDefault
            CompilerError "Macro Call(): unsupported processor"
    CompilerEndSelect
EndMacro


;--------------------------------------------------------------------------------------------------------
;
; END INCLUDE "Sub_ThreadSafe.pbi"
;
;--------------------------------------------------------------------------------------------------------





;XIncludeFile "Sub_ThreadSafe.pbi"

CompilerIf #PB_Compiler_Thread=0
    CompilerError "Enable threadsafe in compiler options - example uses PB strings within the threads"
CompilerEndIf

Procedure RandomAverage(var)

    For i=1 To 100000
        r=Random(2000-1)
        Call(VerifyR)
    Next

    Debug Str(H)+" times more than half (thread "+Str(var)+")"
    Debug Str(L)+" times less than half (thread "+Str(var)+")"
    Debug "sub_1 called "+Str(sub_1_called)+" times, sub_2 called "+Str(sub_2_called)+" times. (thread "+Str(var)+")"

    ProcedureReturn; Quit Proc()
    
    
    Sub(VerifyR)
        If r>1000-1
            H+1
            Call(sub_1)
        Else
            L+1
            Call(sub_2)
        EndIf
    EndSub

    Sub(sub_1)
        sub_1_called + 1
    EndSub

    Sub(sub_2)
        sub_2_called + 1
        Call(sub_1)
    EndSub
EndProcedure


Procedure testthis()
    
    Call(one)
    Call(two)

    ProcedureReturn   ; no problem if you forget it

    Sub(one)
        Debug 1
    EndSub

    Debug "Should never get here, but it is no problem if you forget ProcedureReturn"

    Sub(two)
        Debug 2
    EndSub

EndProcedure


Procedure testthis2(arg1)

    Sub(testthis2_one)
        Debug "In Sub testthis2_one"
        For i = 1 To 2
            Debug "Arg is: "+Str(arg1)
            If arg1 = -1
                Debug "ExitSub"
                ExitSub
                Debug "after ExitSub"
            EndIf
        Next
    EndSub
    
    ;Sub(one)
        ; not allowed, Sub(one) already defined
    ;EndSub
    
    Call(testthis2_one)

EndProcedure


#num_threads = 15

Dim threads(#num_threads)

For i = 1 To #num_threads
    threads(i) = CreateThread(@RandomAverage(),i)
Next i

For i = 1 To #num_threads
    WaitThread( threads(i) )
Next i

Debug "----------------------"

testthis()
Debug 3

Debug "----------------------"

testthis2(123)

Debug "----------------------"

testthis2(-1)

Debug "----------------------"

a = 12

Call(globalsub_2)

Debug "----------------------"

Call(Nested_1)

Debug "OK"

End

Sub(globalsub_1)
    Debug "In Sub globalsub_1"
    Debug a
EndSub

Sub(globalsub_2)
    Debug "In Sub globalsub_2"
    Debug a
    Call(globalsub_1)
EndSub



Sub(Nested_1)
    Debug "In Sub Nested_1"
    Call(Nested_2)
    Debug "Back in Sub Nested_1"
EndSub

Sub(Nested_2)
    Debug "In Sub Nested_2"
    Call(Nested_3)
    Debug "Back in Sub Nested_2"
EndSub

Sub(Nested_3)
    Debug "In Sub Nested_3"
EndSub
Just XIncludeFile one of the 3 files:
- Sub_SingleCall.pbi
- Sub.pbi
- Sub_ThreadSafe.pbi
User avatar
charvista
Addict
Addict
Posts: 949
Joined: Tue Sep 23, 2008 11:38 pm
Location: Belgium

MACRO: Call() Sub() and EndSub (was MACRO: variable evaluati

Post by charvista »

Thank you Danilo!
You have done much more than I hoped :D
Ok, this weekend I will insert these macros in my applications.
If I encounter a problem, I will let you know. But I believe they are perfect. :wink:
I think it is now complete.

I changed the subject to reflect the macros that have been created here.
- Windows 11 Home 64-bit
- PureBasic 6.10 LTS (x64)
- 64 Gb RAM
- 13th Gen Intel(R) Core(TM) i9-13900K 3.00 GHz
- 5K monitor with DPI @ 200%
User avatar
Danilo
Addict
Addict
Posts: 3036
Joined: Sat Apr 26, 2003 8:26 am
Location: Planet Earth

Re: MACRO: Call() Sub() and EndSub (was MACRO: variable eval

Post by Danilo »

charvista wrote:I changed the subject to reflect the macros that have been created here.
The new topic for those macros is Tricks 'n' Tips: [Windows] Sub / EndSub / Call Macros
Post Reply