2

What's purpose of push rdi and pop rdi when calling function in C++? VS2010, x64, debug, no optimizations

C++

int calc()
{
    return 8 + 7;
}

Disassembly:

int calc()
{
000000013F0B1020  push        rdi  
    return 8 + 7;
000000013F0B1022  mov         eax,0Fh  
}
000000013F0B1027  pop         rdi  
000000013F0B1028  ret 
nrz
  • 10,435
  • 4
  • 39
  • 71
Ondrej Petrzilka
  • 1,449
  • 18
  • 24
  • 1
    Could it be some kind of "guard" thing? I think I read somewhere that MSVC *always* leaves some room at the top of each function... – Kerrek SB Feb 03 '13 at 21:45
  • 1
    @KerrekSB, if it was a matter of leaving some "space" at the beginning of the function code, NOOPs would be a much more sensible choice than useless pushes and pops. – Alex D Feb 03 '13 at 22:09

2 Answers2

7

There is no purpose to it. This is a common artifact of unoptimized code. The code generator emits the push edi instruction in anticipation of having to perform an addition. The EDI register must be preserved across function calls. But then, later, figures out that the addition can be performed at compile time.

Getting rid of extraneous code like this requires "peephole optimization". But that optimization isn't enabled in the Debug build. To know what the real code look like, you have to turn on the optimizer, best done by building the Release build. It in fact will completely eliminate the function, you can prevent it from doing so with:

__declspec(noline) int calc()
{
    return 8 + 7;
}

Which produces in the Release build:

    return 8 + 7;
000007F7038E1000  mov         eax,0Fh  
000007F7038E1005  ret  
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
3

Have you heard of "caller-save" and "callee-save" registers?

Since your CPU only has a small, finite number of registers, it's usually impossible for caller/called functions to always use different registers. If a caller function and called function both want to use the same register, it means the value in the caller will have to be saved/restored before/after the call.

Saving/restoring register values can be done either by the caller or by the callee -- which one does so is a matter of convention. The benefit of "caller-save" registers is that if the caller knows it won't need the value in register XYZ after the call, it can omit the save/restore operations. The benefit of "callee-save" registers is that if the callee knows it won't modify the value in register XYZ, it can omit the save/restore operations.

I'm guessing that your compiler treats RDI as a callee-save register, but doesn't omit the unnecessary save/restore operations unless you have compiler optimizations turned on. (If someone knows this is incorrect, please post another answer!)

UPDATE: I found an article on x86 calling conventions: http://en.wikipedia.org/wiki/X86_calling_conventions

It seems to confirm that with most calling conventions, RDI would be callee-save. This doesn't explain why it isn't pushing and popping all the other callee-save registers. Maybe there is something else going on here.

Alex D
  • 29,755
  • 7
  • 80
  • 126
  • Re: "most" calling conventions: There are only 2 mainstream x86-64 calling conventions. In Windows x64, RDI and RSI are call-preserved. In x86-64 System V, RDI and RSI are call-clobbered (and are the first 2 arg-passing registers). [Unable to understand example of cdecl calling convention where caller doesnt need to clean the stack](https://stackoverflow.com/q/49513707). Most 32-bit calling conventions have call-preserved EDI, but 32-bit x86 only has 7 GP registers (not counting the stack pointer), so it's nearly an even split between volatile / non-volatile if you exclude EBP as well. – Peter Cordes Jun 11 '18 at 01:55