Things you should know about registers in PowerBASIC

Started by hutch--, December 20, 2009, 05:14:14 PM

Previous topic - Next topic

hutch--

Writing assembler subs and functions in PowerBASIC is just a little different that writing directly in an assembler. The PB compilers conform to the specifications of Basic as a language and in the process produces subs and functions that already preserve EBX ESI and EDI along with providing a normal stack frame for arguments passed and locals.


#IF 0  ' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

    Something you regularly see in assembler code written in other languages or an assembler is
    code like the following.

    push ebx
    push esi
    push edi

  ; assembler code here

    pop edi
    pop esi
    pop ebx

    This is done to preserve the contents of 3 of the 5 registers that must remain unchanged
    before and after function/sub calls.

    The other two registers ESP & EBP are commonly use to construct what you call a stack frame
    but you must know what you are doing here and the requirement varies from language to language.

    When you code a function or sub in PowerBASIC in assembler you are working in a protected
    environment that already preserves the 5 required registers ESP EBP ESI EDI EBX and of
    those you can commonly use EBX ESI & EDI in your own code as the SUB/FUNCTION design
    already preserves them for you.

    The upside is its a safe and easy to use environment, the downside is you have a slightly
    higher overhead in a function or sub call which effects very short assembler procedures
    but there is a way around that problem, if the procedure is so short that it is effected
    by stack overhead, you simply inline the code and it will be faster than a no-stack-frame
    procedure call.

    When using either of the current 32 bit PB compilers, if you want to write assembler
    code, use the #REGISTER NONE directive to turn off the compiler's own internal
    optimisation techniques otherwise you will get some of the registers remapped to LOCAL
    variables and your code will not work properly.

    Below is a simple proof that a PB sub or function already does the required preservations
    of all of the 5 required registers to be fully complaint with the Microsoft / Intel
    specification on register usage.

#ENDIF ' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

  ' -----------------------------------------------
  ' make a 32 bit variable for each 32 bit register
  ' -----------------------------------------------
    GLOBAL eax_ as DWORD
    GLOBAL ecx_ as DWORD
    GLOBAL edx_ as DWORD

    GLOBAL ebx_ as DWORD
    GLOBAL ebp_ as DWORD
    GLOBAL esp_ as DWORD

    GLOBAL esi_ as DWORD
    GLOBAL edi_ as DWORD

' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

FUNCTION PBmain as LONG

    ! mov esp_, esp
    StdOut hex$(esp_,8)+" --- ESP register content"

    ! mov ebp_, ebp
    StdOut hex$(ebp_,8)+" --- EBP register content"

    ! mov ebx_, ebx
    StdOut hex$(ebx_,8)+" --- EBX register content"

    ! mov esi_, esi
    StdOut hex$(esi_,8)+" --- ESI register content"

    ! mov edi_, edi
    StdOut hex$(edi_,8)+" --- ESP register content"

' ÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·

    teststack       ' call the SUB that modifies registers

' ÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·

    ! mov esp_, esp
    StdOut hex$(esp_,8)+" --- ESP register content"

    ! mov ebp_, ebp
    StdOut hex$(ebp_,8)+" --- EBP register content"

    ! mov ebx_, ebx
    StdOut hex$(ebx_,8)+" --- EBX register content"

    ! mov esi_, esi
    StdOut hex$(esi_,8)+" --- ESI register content"

    ! mov edi_, edi
    StdOut hex$(edi_,8)+" --- ESP register content"

    StdOut chr$(13,10)+"Press any key to exit ...."+chr$(13,10)

    Do
      Sleep 1
    Loop while Inkey$ = ""

    FUNCTION = 0

End FUNCTION

' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

SUB teststack()

    #REGISTER NONE

    StdOut chr$(13,10)+"Hi hi hi, Test Stack Empty Function Here !!!!"+chr$(13,10)

  ' ------------------------------------------
  ' leave ESP and EBP alone or it will go BANG
  ' ------------------------------------------
  ' ! mov esp, &H12345678
  ' ! mov ebp, &H12345678
  ' ------------------------------------------

    ! mov ebx, &H12345678       ' modify EBX, ESI & EDI
    ! mov esi, &H12345678
    ! mov edi, &H12345678

End SUB

' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

hutch--

This example is the corollary to the previous one. While the normal registers are preserved the transient registers may be modified by another external sub or function so if values are stored in any of EAX ECX and EDX before a sub or function call, they must be preserved before the call and restored after it has returned. Normally when you write an algo that is going to call an external sub or function you try to keep values in EBX ESI and/or EDI so they do not have to be preserved before calling an external dub or function.

Here is the test piece that shows that the transient registers are modified by an external call.


#IF 0  ' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

    This example is the corollary to the Microsoft / Intel register convention.
    You can freely modify the 3 volatile or transient registers but so can any other
    protocol compliant sub or function which means if you have values stored in any of
    the EAX ECX or EDX registers they can be changed by and external sub or function.

    This means that if you have values stored in any of these registers before you make
    a call to an external procedure and you need them to be the same after the procedure call
    then you must preserve them.

    As always if you use the PUSH POP mnemonics you must push them then later pop them in
    reverse order.

    push eax
    push ecx
    push edx

    call external function

    pop edx
    pop ecx
    pop eax

    In practice you tend to use the other 3 normally available registers where you can so
    you don't have to do the extra preservations if you can keep the values you need to
    preserve in EBX ESI & EDI.

    Now there is another couple of tricks that you can do with the 3 volatile registers.
    You can implement your own version of FASTCALL by passing up to 3 values in those 3
    registers. You must exercise caution so you don't overwrite them before you store them
    in the proc you have called but its faster than using the stack.

    The other trick, even though its not politically correct and is illegal, immoral and
    fattening, you can pass up to 3 values back from a sub/function using EAX ECX and EDX
    and save doing it some slow messy way.

#ENDIF ' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

    %usemacros = 1

    #include "\pbwin90\include\win32api.inc"

  ' -----------------------------------------------
  ' make a 32 bit variable for each 32 bit register
  ' -----------------------------------------------
    GLOBAL eax_ as DWORD
    GLOBAL ecx_ as DWORD
    GLOBAL edx_ as DWORD

    GLOBAL ebx_ as DWORD
    GLOBAL ebp_ as DWORD
    GLOBAL esp_ as DWORD

    GLOBAL esi_ as DWORD
    GLOBAL edi_ as DWORD

' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

FUNCTION PBmain as LONG

    #REGISTER NONE

    ! mov eax_, eax
    ! mov ecx_, ecx
    ! mov edx_, edx

    StdOut hex$(eax_,8)+" --- EAX register content"
    StdOut hex$(ecx_,8)+" --- ECX register content"
    StdOut hex$(edx_,8)+" --- EDX register content"

' ÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·

    change_regs

' ÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·÷·

    ! mov eax_, eax
    ! mov ecx_, ecx
    ! mov edx_, edx

    StdOut hex$(eax_,8)+" --- EAX register content"
    StdOut hex$(ecx_,8)+" --- ECX register content"
    StdOut hex$(edx_,8)+" --- EDX register content"

    StdOut chr$(13,10)+"Press any key to exit ...."+chr$(13,10)

    Do
      Sleep 1
    Loop while Inkey$ = ""

    FUNCTION = 0

End FUNCTION

' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

SUB change_regs()

    #REGISTER NONE

    ! mov eax, &H12345678
    ! mov ecx, &H12345678
    ! mov edx, &H12345678

END SUB

' ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php