Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does each PUSH instruction push a multiple of 8 bytes on x64?

On x64, does each PUSH instruction push a multiple of 8 bytes? If not, how much does it push?

Also, how much stack space does each function parameter consume?

like image 463
Demi Avatar asked Oct 28 '16 13:10

Demi


2 Answers

PUSH Operand Size in 64-bit mode

The size of the value pushed on the stack and the amount that the stack pointer is adjusted by depends on the operand size of the PUSH instruction. In 64-bit mode the operand size can only be 16-bit or 64-bit. It's not possible to encode a 32-bit PUSH instruction in 64-bit mode and it's not possible to encode an 8-bit PUSH instruction in any mode.

For example, these are all 64-bit PUSH instructions:

push    rax
push    1              ; 8-bit immediate sign-extended to 64 bits
push    65536          ; 32-bit immediate sign-extended to 64 bits
push    QWORD PTR[0]
push    fs             ; 16-bit segment register zero-extended to 64 bits

The above instructions all subtract 8 from RSP and then write a 64-bit value to the location pointed to by RSP.

These are all 16-bit PUSH instructions:

push    ax
push    WORD PTR[0]

These instructions subtract 2 from RSP and then write a 16-bit value to the location pointed by RSP. Because they badly misalign the stack, using a 16-bit PUSH in 64-bit mode is pretty much always a mistake. Instead you should load the 16-bit value into a register (if not already there), extend it as necessary, and then use a 64-bit PUSH.

The following instructions are illegal and can't be encoded in 64-bit mode:

push    al
push    eax
push    BYTE PTR[0]
push    DWORD PTR[0]
push    0100000000h    ; 64-bit immediate value isn't supported

Pushing an 8-bit or 32-bit value on the stack requires loading the value into a register, extending it and then using a 64-bit PUSH, just like you should do with 16-bit values.

Parameter Passing in 64-bit mode

Generally speaking, in 64-bit mode function arguments aren't passed on stack. Both the Microsoft and Linux 64-bit x86 calling conventions pass most arguments in registers. The stack is only used when there's not enough room in registers to pass the arguments to a function. In that case each argument takes up one or more 8 byte stack slots. Note that compilers won't necessarily use PUSH instructions to place these arguments onto the stack. A common strategy is to allocate enough space on the stack for all of a function's outgoing arguments in the function prologue and then use MOV instructions to put arguments on the stack as necessary.

like image 92
Ross Ridge Avatar answered Oct 11 '22 13:10

Ross Ridge


No, but in practice, one always pushes an 8 byte value onto the stack.

Function parameters consuming varying amounts of stack space depending on the size of the function parameter and whether it is passed in the stack, in the registers, or passed by reference.

If one passes a function parameter in the stack by pushing, then the fact that there are convenient push instructions that pushes 8 bytes strongly suggests that you pass the parameter as an 8 byte value. For pointers, int64 and plain doubles, this is obviously easy. For char, bool, short, and other types whose memory size is smaller, what most compilers do is push the value in an 8 byte chunk. Types that take 16 or 32 bytes might be pushed by the compiler with several push instructions. Bigger values tend not to get passed by pushing; often a compiler tries to pass a pointer to a bigger value rather than pass the value itself. {I've built a compiler that can pass arbitrarily big values, but it does so by making space in the stack, and then executing a block move instruction]. Details vary from compiler to compiler, and according to the language semantics of the program being compiled.

A really clever compiler might notice that several arguments are small and can be packed into an 8 byte quantity that only requires a single push. I've not seen one actually do that, probably because it takes work to pack such values together into a register, and push instructions are already pretty fast by design and by cache.

It is possible to push smaller values onto the stack. This is legal according to the architecture, but is likely to cause a mis-aligned access performance hit if the set of small values pushed isn't a multiple of 8 bytes. And then one must be careful to pop off the non-multiple correctly to restore stack alignment. Not useful in my experience (see code golf comment by Peter Cordes).

If you pass the value in a register, nothing gets pushed :-}

One might arrange to store parameter values in a well known locations in the stack. Then there isn't any push :-}

like image 45
Ira Baxter Avatar answered Oct 11 '22 14:10

Ira Baxter