News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests

Main Menu

What is register preserving?

Started by etairi, December 31, 2017, 06:10:37 AM

Previous topic - Next topic

etairi

While reading the Microsoft Docs, I came across the following table about register usage. It says that some of the registers must be preserved. What exactly does it mean? Also when checking the caller/callee saved registers page, there are list of registers that are volatile and nonvolatile registers. And the nonvolatile ones need to be saved or restored. Does it mean I need to push and pop all the nonvolatile registers that I use in my functions?

jj2007

Yes, in 32-bit code it's push & pop, see here.
In 64-bit code it can be push & pop, but only under certain tricky conditions (->alignment, shadow space). Better define a local variable:

MyTest proc
Local @rsi:QWORD
  mov @rsi, rsi
  ...
  mov rsi, @rsi
  ret
MyTest endp

raymond

QuoteDoes it mean I need to push and pop all the nonvolatile registers that I use in my functions?

The answer to that question could be YES or NO; it all depends on the kind of code you intend to write.

If you intend to reuse part of your code in other programs, you definitely need to preserve such registers. Your other programs would expect that in the same way that you expect Windows functions to preserve them.

If you intend to use repeatable code within a PROC using local variables, you certainly need to preserve the EBP register at the very least. There may not be any need to preserve other registers (apart from the ESP) as long as you don't have any need for such preservation in the calling code.

In the 'old' days prior to WinXP, it was important to preserve all the nonvolatile registers; otherwise, the program could crash after calling a Windows function. The MS programmers finally woke up and preserved those registers themselves within the OS. If you write code to be used with Win98 or earlier, you then need to preserve them all!

I write many short programs to solve math problems for example. I don't normally use any formal PROC and use the EBX, ESI, EDI and EBP freely without preservation within the code. The only detail I watch is the stack balance (ESP). Such program function perfectly and return to the OS without any glitch.

And, for your information, the FPU registers are all considered volatile by the OS. If those contain any useful data, make sure to preserve such data before calling any Windows function.
Whenever you assume something, you risk being wrong half the time.
http://www.ray.masmcode.com

jj2007

Quote from: raymond on January 02, 2018, 06:49:40 AMIn the 'old' days prior to WinXP, it was important to preserve all the nonvolatile registers; otherwise, the program could crash after calling a Windows function. The MS programmers finally woke up and preserved those registers themselves within the OS.

Dear Raymond,
Do you have any official document regarding this behaviour? Occasionally I forget to preserve esi edi ebx in a WndProc, and it never crashes, but I wonder whether this has ever been documented...

There is an old preservation of esi, edi, ebx in general code thread, but it didn't come to a conclusion. GCC, for example, preserves them:00401340   Ú.  55                push ebp
00401341   ³.  57                push edi
00401342   ³.  56                push esi
00401343   ³.  53                push ebx
00401344   ³.  81EC 8C010000     sub esp, 18C
0040134A   ³.  8BB424 A0010000   mov esi, [esp+1A0]
00401351   ³.  8B9C24 A4010000   mov ebx, [esp+1A4]
00401358   ³.  83FB 02           cmp ebx, 2                           ; WM_DESTROY, cascaded IF (messages 1..111, 4 exits)
0040135B   ³. 0F84 20020000     je 00401581
00401361   ³.  81FB 11010000     cmp ebx, 111                         ; WM_COMMAND
00401367   ³. 0F84 71010000     je 004014DE
0040136D   ³.  83FB 01           cmp ebx, 1                           ; WM_CREATE
00401370   ³. 0F85 1A020000     jne 00401590
00401376   ³.  E8 1D0C0000       call <jmp.&USER32.CreateMenu>        ; [USER32.CreateMenu, case 1 (WM_CREATE) of cascaded IF Tmp.401358
0040137B   ³.  89C5              mov ebp, eax
0040137D   ³.  E8 1E0C0000       call <jmp.&USER32.CreatePopupMenu>   ; [USER32.CreatePopupMenu


The same source compiled with VC shows no register preservation; however, VC doesn't use esi edi ebx, either, so that is no proof 8)

hutch--

With 64 bit Windows code, the important factor is procedure alignment and this has to be correct at the procedure entry point. When you use PUSH / POP you alter the stack pointer RSP and unless you know exactly what you are doing and why, you risk messing up the alignment and by doing so the app will exit with no warning or explanation. Win64 has more registers to start with so there is less pressure to have to deal with the non-volatile registers but if you need more registers, the method that JJ explained is the safe way to do this. Allocate 64 bit LOCAL variables and copy the non-volatile register into it and at the end of the procedure, copy the local back to the register.

hutch--

The Intel 32 bit ABI was started on Windows platforms with NT3.5 from memory and was standard in WinNT4. It remains current in 32 bit Windows code. If you bother to have a good look at the Intel ABI, you will see how it works and why it is done. The OS controls the non-volatile registers for its own use and if you exit a procedure with a modified non-volatile register, you risk the OS shutting down the application with a general protection fault.

I have always wondered why some programmers play Russian Roulette with register conventions, get something to work on one OS version but it crashes on the next. Code written correctly for Win95 OEM runs correctly on Win10 32 bit subsystem. The general rule is to observe the Intel ABI and you code will run correctly from one OS version to another. Play Russian Roulette and you risk it exploding in you face on another OS version and make a jerk out of you.

Notwithstanding the above, once you know how to do it properly, writing simple test apps lets you get away with sloppy code in a single procedure app and they are often handy when you are writing "write once, run once" code but try it in production code and you are in trouble.

raymond

QuoteDo you have any official document regarding this behaviour?

Nothing official, only own experience.

A few years ago, I had written a program to provide a specific slide show for some 600 pictures distributed in about a dozen subfolders. It had been developed on a WinXP OS and ran perfectly. However, I found out later that it would crash when trying to run it on a different computer under Win98.

Upon examination, the one and only cause of the problem was a single instance of preserving the ESI/EDI registers on the stack and restoring them later in the wrong order :icon_redface:. Simply exchanging those two bytes in the .exe file with a hex editor solved the crashing problem when running on that Win98 computer.
Whenever you assume something, you risk being wrong half the time.
http://www.ray.masmcode.com

daydreamer

I Think for Writing recursive coding, you need to use macros like this for your favourite registers you wanna code with,its for 32bit but can be customized for 64bit to what register usage you need to push/pop or customized to make use of virtual stack for general 64bit regs
its from unfinished try to make procedurally generated tree in masm,inspired by fluxus script
fluxus is what makes all that patterns in your mediaplayer

.data?
    statestack  dd 0 dup (65536)
                dd 0 dup (65536)

.data
ALIGN 16
vectconst0   REAL4 0.0,0.0,0.0,0.0 ;room for constanst used in fluxusprogram
vectconst1   REAL4 0.0,0.0,0.0,0.0
vectconst2   REAL4 0.0,0.0,0.0,0.0
vectconst3   REAL4 0.0,0.0,0.0,0.0
vectconst4   REAL4 0.0,0.0,0.0,0.0
vectconst5   REAL4 0.0,0.0,0.0,0.0
vectconst6   REAL4 0.0,0.0,0.0,0.0
vectconst7   REAL4 0.0,0.0,0.0,0.0
vectconst8   REAL4 0.0,0.0,0.0,0.0
tmpxmm0      REAL4 0.0,0.0,0.0,0.0 ;temporary storage
tmpxmm1      REAL4 0.0,0.0,0.0,0.0
tmpxmm2      REAL4 0.0,0.0,0.0,0.0
tmpxmm3      REAL4 0.0,0.0,0.0,0.0
tmpxmm4      REAL4 0.0,0.0,0.0,0.0
tmpxmm5      REAL4 0.0,0.0,0.0,0.0
tmpxmm6      REAL4 0.0,0.0,0.0,0.0
tmpxmm7      REAL4 0.0,0.0,0.0,0.0
tmpf0        REAL4 0.0,0.0,0.0,0.0
tmpf1        REAL4 0.0,0.0,0.0,0.0
tmpf2        REAL4 0.0,0.0,0.0,0.0
X   equ 0
Y equ 4
Z equ 8
.code

; #########################################################################
    PUSHSTATE MACRO
    FXSAVE [ebx]
    add ebx,512
    ENDM
    POPSTATE MACRO
    sub ebx,512
    FXRSTOR [ebx]
   
    ENDM
   
    TRANSLATE MACRO vectconst
    addps XMM0,vectconst
    addps XMM1,vectconst
    addps XMM2,vectconst
    addps XMM3,vectconst
    addps XMM4,vectconst
    addps XMM5,vectconst
    addps XMM6,vectconst
    addps XMM7,vectconst

    ENDM
    SCALE MACRO vectconst
    mulps XMM0,vectconst
    mulps XMM1,vectconst
    mulps XMM2,vectconst
    mulps XMM3,vectconst
    mulps XMM4,vectconst
    mulps XMM5,vectconst
    mulps XMM6,vectconst
    mulps XMM7,vectconst
    ENDM
   
my none asm creations
https://masm32.com/board/index.php?topic=6937.msg74303#msg74303
I am an Invoker
"An Invoker is a mage who specializes in the manipulation of raw and elemental energies."
Like SIMD coding