Will GetCaller() macro work in PB v6?

Just starting out? Need help? Post your questions and find answers here.
User avatar
skywalk
Addict
Addict
Posts: 4211
Joined: Wed Dec 23, 2009 10:14 pm
Location: Boston, MA

Will GetCaller() macro work in PB v6?

Post by skywalk »

I prefer prototypes instead of "procedure xyz() for somestructure".
However, this requires the inline ASM macro GetCaller() to work in the proposed PB -> emit C -> compiler.
Will the code below work in PB v6?

Code: Select all

;////////////////////////////////////////////////
; REV:  051121, skywalk
;       millisecond timer class for medium time critical measurements.
;       Uses Prototypes in Structure instead of Interfaces and Virtual Table pointers.
;       Simpler to build, but carries redundancy in subsequent calls.
;       Ex. t1\Start(t1) ;<-- t1 must be passed to itself if GetCaller(t1) fails.
; REV:  120901, skywalk
;       Updated To PB. Inspired by PB OOP examples from Hydrate, ts-soft
; REV:  121223, skywalk
;       Collapsed GetTime() to Stop().
; Format for classes:
;   PROTOTYPES      - Only necessary for methods not in parent classes. Declared 1st so structure can read it.
;   STRUCTURE       - Variables, then Methods. Order unimportant. - METHODNAME.PROTOTYPE
;   METHODS         - Use prefix 'CLASSNAME_' to distinguish between procedures.
;   PROPERTIES      - Get/Set using Structure Fields.
;   CONSTRUCTOR     - Not in structure and defined last to set all method pointers appropriately.
;   DESTRUCTOR      - Not in structure.
; Requires Windows APIs:
; BOOL QueryPerformanceCounter  (LARGE_INTEGER *lpPerformanceCount) ; kernel32.dll
; BOOL QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency)        ; kernel32.dll
; NOTES:
;   cTimer reports units in seconds! Use Eng notation to determine ms, ns, etc.
;   PB v5.21b2 adds QueryPerformanceCounter_() for ElapsedMilliseconds().
;   But return value will wrap after 49.7 days due to integer instead of quad.
;   Steps to use cTimer.
;     1. Define t1.cTimer
;     2. Call:  cTimer_New(t1)
;     3. t1\Start()...do something...t1\Stop()
;     4. Read out Stop Time or Running Time:
;        Debug t1\Stop()      ; Elapsed time(s) between Last Start() and This Stop()
;        Debug t1\RunTime()   ; Total running time(s) since 1st Start()
;     5. Call:  cTimer_Free(t1)
;-} END cTimer.pbi Header
EnableExplicit
CompilerIf #PB_Compiler_Version > 573
  CompilerError "Warning: Check ASM-Compiler for valid RBP-Register!"
CompilerEndIf
Macro GetCaller(me) ; Get struct pointer from caller
  CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
    !mov [p.p_me], rbp
  CompilerElse
    !mov [p.p_me], ebp
  CompilerEndIf
EndMacro
Prototype   cTimer_OverHead();*me)
Prototype   cTimer_Start()   ;*me)
Prototype.d cTimer_Stop()    ;*me)
Prototype.d cTimer_RunTime() ;*me)
Structure cTimer
  ri.i                          ; API return values
  HiResCounter.i                ; 1 = HiResCounter enabled, 0 = Fail
  Freq.q                        ; Counter Resolution(ticks/sec),     Ex. i5-2540M @ 2.6GHz ~= 390nsec
  OverHead.q                    ; Ticks, (not seconds) of API calls, Ex. i5-2540M @ 2.6GHz ~= 13ticks
  Period.q                      ; Length of Time(ticks) between Timer Start and Stop.
  StartTick.q                   ; Start(ticks) used to determine Time Period
  StartTick1st.q                ; Remember 1st Start Tick for Timer total run time.
                                ; Method.Prototype section
  GetOverHead.cTimer_OverHead   ; Determine overhead of API calls
  Start.cTimer_Start            ; Start the Timer
  Stop.cTimer_Stop              ; Stop the Timer and determine Period, then Read out Time Period between Last Start & This Stop
  Run_Time.cTimer_RunTime       ; Read out Running Time from 1st Start
EndStructure
Procedure cTimer_OverHead();*me.cTimer)
                           ; Determine Overhead(ticks) of API calls using 30 averages.
  Protected *me.cTimer
  GetCaller(*me)
  Protected.i i
  Protected.q Tick1, avg
  *me\OverHead = 0
  For i = 1 To 30
    *me\ri = QueryPerformanceCounter_(@Tick1)
    Delay(0)  ;<-- Overhead = 0 without break between 2 calls.
    *me\ri = QueryPerformanceCounter_(@avg)
    avg - Tick1
    *me\OverHead + avg
  Next i
  *me\OverHead / i
EndProcedure
Procedure cTimer_Start();*me.cTimer)
  Protected *me.cTimer
  GetCaller(*me)
  *me\ri = QueryPerformanceCounter_(@*me\StartTick)
  If *me\StartTick1st = 0
    *me\StartTick1st = *me\StartTick
  EndIf
EndProcedure
Procedure.d cTimer_Stop();*me.cTimer)
                         ; This does not end the Timer.
                         ; Update Time Elapsed between this StopTimer() and previous StartTimer() calls.
  Protected *me.cTimer
  GetCaller(*me)
  Protected.q Tick2
  *me\ri = QueryPerformanceCounter_(@Tick2)
  *me\Period = Tick2 - *me\StartTick
  ; StopTime(seconds) uses TimePeriod from last call of cTimer_Stop().
  ; Otherwise, StopTime holds its previous value.
  ; Subsequent calls to cTimer_Stop() change/update StopTime.
  ; Start() followed by immediate Stop() will reveal OverHead or negative times.
  If *me\StartTick1st
    ProcedureReturn (0.0 + *me\Period - *me\OverHead) / *me\Freq
  EndIf
EndProcedure
Procedure.d cTimer_RunTime();*me.cTimer)
                            ; RunTime(s) from 1st cTimer_Start(). Resets if *me\StartTick1st = 0 or new creation of class cTimer.
  Protected *me.cTimer
  GetCaller(*me)
  Protected.q Tick2
  If *me\StartTick1st
    *me\ri = QueryPerformanceCounter_(@Tick2)
    ProcedureReturn (0.0 + Tick2 - *me\StartTick1st - *me\OverHead) / *me\Freq
  EndIf
EndProcedure
Procedure cTimer_Free(*me.cTimer)
  ;- cTimer DESTRUCTOR
  ; Terminate/Free the class cTimer
  *me\Freq = 0
  *me\Period = 0
  *me\StartTick = 0
  *me\StartTick1st = 0
  *me\OverHead = 0
  *me\HiResCounter = 0
  *me\ri = 0
  ClearStructure(*me, cTimer)
EndProcedure
Procedure cTimer_New(*me.cTimer)
  ;- cTimer CONSTRUCTOR
  ; Define Method/Procedure pointers
  *me\GetOverHead         = @cTimer_OverHead()
  *me\Start               = @cTimer_Start()
  *me\Stop                = @cTimer_Stop()
  *me\Run_Time            = @cTimer_RunTime()
  *me\Period = 0
  *me\StartTick = 0
  *me\StartTick1st = 0
  ; Check for high-resolution performance counter, then determine timing overhead
  If QueryPerformanceFrequency_(@*me\Freq)
    *me\HiResCounter = 1
    *me\GetOverHead();*me)
                     ;*me\Start()
                     ;*me\Start()
                     ;Delay(0)
                     ;*me\Stop()
  EndIf
EndProcedure
;-} END cTimer.pbi
;-{ TEST
CompilerIf 1  ;-! SET TEST
              ;-{ XINCLUDES
  #nNull              = -999.00000000000000000
  #small1             = 1e-16       ; Epsilon value: small '1' to add to double calcs to round up or prevent overflows.
  #small1_EQ          = 1e-9        ; Epsilon value: small '1' for IsEqual comparisons
  #CRLF$              = Chr(13) + Chr(10)
  #DASH$              = "-"
  #PRD$               = "."
  #CMA$               = ","
  #COLON2$            = "::"
  Macro IsInteger(x)
    ; Bool(x = Round(x, #PB_Round_Down))  ; Fails in the following case.
    ; x.d = 11.1 / 10.0 * 100.0   ; 110.99999999999999
    ; y.d = 11.1 * 100.0 / 10.0   ; 111.0
    Bool(Abs((x) - Round(x, #PB_Round_Nearest)) <= #small1)  ; Use Epsilon value instead.
  EndMacro
  Macro PB_CP
    ;ProgramFilename() + #COLON2$ + #PB_Compiler_File + #COLON2$ + #PB_Compiler_Procedure
    GetFilePart(#PB_Compiler_File, #PB_FileSystem_NoExtension) + #COLON2$ + #PB_Compiler_Procedure
  EndMacro
  Macro MRQYN(txt)  ; MessageRequester Question - [Yes] | No
    MessageRequester(PB_CP, txt, #MB_ICONQUESTION | #MB_YESNO | #MB_DEFBUTTON1)
  EndMacro
  Procedure.s StrDex(X.d, numsd.i=6, SI_units.i=0)
    ; REV:  100405, skywalk
    ; Goal:   Shrink overall width of result, instead of fixed width num2eng()
    ;         Leading/Trailing zeros including exponent and decimal point removed.
    ; Syntax:  Xstr = StrDEX(X,numsd,@dropexp=0)
    ; IN:
    ;   x = real number to be formatted.
    ;   numsd = integer specifying the total number of significant digits.
    ;           "aa.bbbe+3" -> 2 + 3 = 5
    ;   *dropexp\i = 1 removes the exponent from the result
    ; RETURN:
    ;   Xstr = string format of X using numsd significant digits in engineering notation.
    ;   *dropexp\i = exp value that was dropped from the Xstr
    Protected.i exp, nsgn
    Protected.s r$, Sgn$
    If X > 0
      Sgn$ = #Empty$    ; Goal is to shrink the overall width of the result.
    ElseIf X < 0
      Sgn$ = #DASH$    ; '-'
      X * -1
      nsgn = 1
    Else
      ProcedureReturn "0"
    EndIf
    If x <> #nNull And ((IsInteger(x) And x > 1000) Or x < 0.001)
      exp = Round(Log10(X),#PB_Round_Down)
      If exp > 0
        exp / 3 * 3
      Else
        exp = (-exp + 3) / 3 * (-3)
      EndIf
      X = X * Pow(10,-exp)
      If X >= 1000
        X / 1000
        exp + 3
      EndIf
      If X >= 100
        numsd - 3
      ElseIf X >= 10
        numsd - 2
      Else
        numsd - 1
      EndIf
      If numsd < 0
        numsd = 0
      EndIf
      If nsgn
        r$ = sgn$ + StrD(x - #small1,numsd)
      Else
        r$ = sgn$ + StrD(x + #small1,numsd)
      EndIf
      ;r$ = RTrim(r$,"0")
      r$ = RTrim(r$,#PRD$)
      If Not SI_units
        r$ + "e" + Str(exp)
      Else
        If exp >= 0
          ; SI Unit  exp:prefix names:  3:kilo,   6:Mega,      9:Giga,  12:Tera,  15:Peta,   18:Exa,   21:Zetta,  24:Yotta
          r$ + StringField(",k,M,G,T,P,E,Z,Y", exp/3+1, #CMA$)
        Else
          ; SI Unit -exp:prefix names: -3:milli, -6:micro(u), -9:nano, -12:pico, -15:femto, -18:atto, -21:zepto, -24:yocto
          r$ + StringField(",m,u,n,p,f,a,z,y", -exp/3+1, #CMA$)
        EndIf
      EndIf
    Else
      If numsd < 0
        numsd = 0
      EndIf
      If nsgn
        r$ = sgn$ + StrD(x - #small1,numsd)
      Else
        r$ = sgn$ + StrD(x + #small1,numsd)
      EndIf
      If numsd
        r$ = RTrim(r$,"0")
      EndIf
      r$ = RTrim(r$,#PRD$)
    EndIf
    ProcedureReturn r$
  EndProcedure
  ;-} XINCLUDES
  Define.s r$
  CompilerIf #PB_Compiler_Debugger = 1
    ;CompilerWarning "Debugger OFF for accurate timings!"
    r$ + "Turn Debugger OFF for accurate timings!" + #CRLF$
  CompilerEndIf
  ; Create/use 2 unique timers.
  Define t1.cTimer, t2.cTimer
  If 1
    cTimer_New(t2)
    cTimer_New(t1)
    r$ + "-- t1\Start(),t2\Start() --"
    t1\Start()
    t2\Start()
    r$ + #CRLF$ + "t2\Stop()-t1\Stop() = " + StrDex(t2\Stop()-t1\Stop(),6,1)
    r$ + #CRLF$ + "t1\HiResCounter     = " + Str(t1\HiResCounter)
    r$ + #CRLF$ + "t1\OverHead         = " + Str(t1\OverHead)
    r$ + #CRLF$ + "t1\StartTick        = " + Str(t1\StartTick)
    r$ + #CRLF$ + "-- Delay(1000) --"
    Delay(1000)
    r$ + #CRLF$ + "t1\Stop()           = " + StrDex(t1\Stop(),6,1)
    r$ + #CRLF$ + "-- t1\Start() --"
    t1\Start()
    r$ + #CRLF$ + "-- Delay(2000) --"
    Delay(2000)
    r$ + #CRLF$ + "t1\Stop()           = " + StrDex(t1\Stop(),6,1)
    r$ + #CRLF$ + "t2\Stop()           = " + StrDex(t2\Stop(),6,1)
    r$ + #CRLF$ + "t1\Run_Time()       = " + StrDex(t1\Run_Time(),6,1)
    r$ + #CRLF$ + "t2\Run_Time()       = " + StrDex(t2\Run_Time(),6,1)
    r$ + #CRLF$ + "-- cTimer_Free(t1) --"
    cTimer_Free(t1)   ; Kill t1
    cTimer_Free(t2)   ; Kill t1
                      ;Debug t1\Stop()   ;<-- ERROR, undefined object after cTimer_Free().
  EndIf
  If 0
    cTimer_New(t1)
    r$ + #CRLF$ + "-- t1\Start() --"
    t1\Start()
    r$ + #CRLF$ + "t1\HiResCounter     = " + Str(t1\HiResCounter)
    r$ + #CRLF$ + "t1\OverHead         = " + Str(t1\OverHead)
    r$ + #CRLF$ + "t1\StartTick        = " + Str(t1\StartTick)
    r$ + #CRLF$ + "-- Delay(100) --"
    Delay(100)
    r$ + #CRLF$ + "t1\Stop()           = " + StrDex(t1\Stop(),6,1)
    r$ + #CRLF$ + "-- t1\Start() --"
    t1\Start()
    r$ + #CRLF$ + "t1\Stop()           = " + StrDex(t1\Stop(),6,1)
    r$ + #CRLF$ + "t1\Run_Time()       = " + StrDex(t1\Run_Time(),6,1)
    r$ + #CRLF$ + "-- Delay(500) --"
    Delay(500)
    r$ + #CRLF$ + "t1\Stop()           = " + StrDex(t1\Stop(),6,1)
    r$ + #CRLF$ + "t1\Run_Time()       = " + StrDex(t1\Run_Time(),6,1)
    r$ + #CRLF$ + "-- Delay(50) --"
    Delay(50)
    r$ + #CRLF$ + "t1\Stop()           = " + StrDex(t1\Stop(),6,1)
    r$ + #CRLF$ + "t1\Run_Time()       = " + StrDex(t1\Run_Time(),6,1)
    r$ + #CRLF$ + "-- t1\Start() --"
    t1\Start()
    r$ + #CRLF$ + "-- Delay(50) --"
    Delay(50)
    r$ + #CRLF$ + "t1\Stop()           = " + StrDex(t1\Stop(),6,1)
    r$ + #CRLF$ + "t1\Run_Time()       = " + StrDex(t1\Run_Time(),6,1)
    r$ + #CRLF$ + "-- cTimer_Free(t1) --"
    cTimer_Free(t1)   ; Kill t1
  EndIf
  If 0
    cTimer_New(t1)
    cTimer_New(t2)
    t1\Start()
    t2\Start()
    r$ + #CRLF$ + "t1\HiResCounter     = " + Str(t1\HiResCounter)
    r$ + #CRLF$ + "t1\OverHead         = " + Str(t1\OverHead)
    r$ + #CRLF$ + "t1\StartTick        = " + Str(t1\StartTick)
    t2\Stop()
    r$ + #CRLF$ + "-- t2 stopped immediately, Delay(0) --"
    r$ + #CRLF$ + "t2\Stop()           = " + StrDex(t2\Stop(),6,1)
    Delay(100)
    r$ + #CRLF$ + "-- Delay(100) --"
    r$ + #CRLF$ + "t1\Stop()           = " + StrDex(t1\Stop(),6,1)
    Delay(100)
    r$ + #CRLF$ + "-- Delay(100) but without Stop --"
    r$ + #CRLF$ + "-- t1 readout unchanged --"
    r$ + #CRLF$ + "t1\Stop()           = " + StrDex(t1\Stop(),6,1)
    r$ + #CRLF$ + "-- t1 readout after Stop --"
    r$ + #CRLF$ + "t1\Stop()           = " + StrDex(t1\Stop(),6,1)
    r$ + #CRLF$ + "-- t1 readout after another Stop --"
    r$ + #CRLF$ + "t1\Stop()           = " + StrDex(t1\Stop(),6,1)
    ; Kill t1 and redefine...
    cTimer_Free(t1)
    Define.cTimer t1
    cTimer_New(t1)
    Delay(100)
    r$ + #CRLF$ + "-- t1 after Init() + delay(100) --"
    r$ + #CRLF$ + "t1\Stop()           = " + StrDex(t1\Stop(),6,1)
    r$ + #CRLF$ + "t1\Run_Time()       = " + StrDex(t1\Run_Time(),6,1)
    r$ + #CRLF$ + "-- t1 after t1\Start() + delay(200) --"
    t1\Start()
    Delay(200)
    r$ + #CRLF$ + "t1\Stop()           = " + StrDex(t1\Stop(),6,1)
    r$ + #CRLF$ + "t1\Run_Time()       = " + StrDex(t1\Run_Time(),6,1)
    r$ + #CRLF$ + "-- t1 after another delay(200) --"
    Delay(200)
    r$ + #CRLF$ + "t1\Stop()           = " + StrDex(t1\Stop(),6,1)
    r$ + #CRLF$ + "t1\Run_Time()       = " + StrDex(t1\Run_Time(),6,1)
    ; Kill t1,t2
    cTimer_Free(t1)
    cTimer_Free(t2)
  EndIf
  CompilerIf #PB_Compiler_Debugger = 0
    If MRQYN("cTimer Test - Copy To Clipboard?" + #CRLF$ + r$) = #PB_MessageRequester_Yes
      SetClipboardText(r$)
    EndIf
  CompilerElse
    Debug r$
  CompilerEndIf
CompilerEndIf
;-} TEST
The nice thing about standards is there are so many to choose from. ~ Andrew Tanenbaum
User avatar
Mijikai
Addict
Addict
Posts: 1517
Joined: Sun Sep 11, 2016 2:17 pm

Re: Will GetCaller() macro work in PB v6?

Post by Mijikai »

I dont see why this should not be possible but we have to wait.
In the meantime you can simply provide the pointer as paramater (whats usually done when doing oop).
I would suggest a vTable to reduce size i personally dont like storing redundant information.
User avatar
skywalk
Addict
Addict
Posts: 4211
Joined: Wed Dec 23, 2009 10:14 pm
Location: Boston, MA

Re: Will GetCaller() macro work in PB v6?

Post by skywalk »

The point of the code is to use GetCaller() to NOT pass a pointer for each call?
t1\Stop() instead of t1\Stop(t1)?
And to NOT use *VTable.
The nice thing about standards is there are so many to choose from. ~ Andrew Tanenbaum
Fred
Administrator
Administrator
Posts: 18162
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Re: Will GetCaller() macro work in PB v6?

Post by Fred »

From what I understand, you're relying on very specific PB generated code to grab the structured object address in your function. It could be possible on C backend using assembly code as well, but will be C compiler dependant and probably optimisation switch dependant as well. IMHO, you should use interface or pass the self pointer to the function to be on the safe side (performance wise it should be the same)
Rinzwind
Enthusiast
Enthusiast
Posts: 679
Joined: Wed Mar 11, 2009 4:06 pm
Location: NL

Re: Will GetCaller() macro work in PB v6?

Post by Rinzwind »

That GetCaller breaks when you start using nested structures (fancy word: composition).

Also do not see the worry here. Even when an upgraded structure is implemented (big if, the request is not new), your code is easy to update and more robust and simple in return.

The PB-C-compiler will maybe/probably do things different in ASM. Anyway, PB-ASM-compiler will stay available for years for sure.

Btw Fiddled with this myself too: viewtopic.php?f=12&t=75529
Hence the request to add syntax to make this style easy and elegant to use. Just needs minor additions for large gains 8)
User avatar
skywalk
Addict
Addict
Posts: 4211
Joined: Wed Dec 23, 2009 10:14 pm
Location: Boston, MA

Re: Will GetCaller() macro work in PB v6?

Post by skywalk »

@Fred - Admittedly, I am a low class user. :)
Meaning, very little OOP use. I prefer the lib_DoThis() approach.
Preprocessor approaches are flat out.
I can adapt my various class code to either VTable or a redundant t1\Stop(t1) if no other mechanism emerges.

@Rinzwind - Yes, I posted in your other thread and understand the limitations of my snippet.
I do not attempt nested structures in this case. That would prompt a VTable or passing the substructure pointer.
The nice thing about standards is there are so many to choose from. ~ Andrew Tanenbaum
Post Reply