3

Enter in function, standard prologue

push rbp
mov rbp, rsp
sub rsp, 128 ; large space for storing doubles, for example

How to reference local variables now, via rsp + positive offset, or via rbp + negative offset?

Reading https://en.wikibooks.org/wiki/X86_Disassembly/Functions_and_Stack_Frames, indeed quite understandable. It writes

...the value of esp cannot be reliably used to determine (using the appropriate offset) the memory location of a specific local variable. To solve this problem, many compilers access local variables using negative offsets from the ebp registers.

Why not reliable? Until that question I was accessing local variables via rsp, like this:

mov rax, [rsp+.length] ; get length of array
mov [rsp+8], rax ; store sum at the stack

everything goes quite nicely using rsp for stack referencing.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Bulat M.
  • 680
  • 9
  • 25
  • 1
    The stack pointer *can* often be used to determine the address of variables on the stack. However, if the function uses variable-length arrays or the equivalent of `alloca()`, using offsets from the stack pointer may no longer be possible. – EOF Sep 17 '16 at 13:51
  • @EOF, so one should use addressing relative to rsp in all cases except that two? – Bulat M. Sep 17 '16 at 14:34

1 Answers1

5

Look at gcc output. It defaults to -fomit-frame-pointer when optimizing, only making a stack frame when functions use variable-length arrays or it needs to align the stack to more than 16B.

That wiki page is basically wrong. There's no scary weird stuff that makes it "unreliable". The only time you can't do it is when you need to modify RSP by an amount that isn't an assemble-time constant.


However, if you do make a stack frame with push rbp / mov rbp, rsp, you should use RBP-relative addressing modes. It's more efficient, because [rsp + 8] takes an extra byte to encode (vs. [rbp - 8]). Addressing modes with RSP as the base register always need a SIB byte, even when there's no index register.

The point of using RSP-relative addressing modes is that you can avoid wasting instructions making a stack frame, so RBP is just another call-saved register (like RBX) that you can save/restore and use for whatever you want.


The other big advantage to RBP-relative addressing is that the offset from RBP to a given variable stays constant for the entire function. Unlike compilers, we puny humans are easily confused by pushes and pops which change RSP inside a function. Of course, 64-bit code hardly ever changes RSP inside a function between the prologue and epilogue, because both ABIs pass args in registers. Saving/restoring some call-preserved registers (like RBX or R12-R15) in the prologue/epilogue is often better than having push/pop inside the function (and definitely better than inside a loop). When you need to spill/reload, mov for random access is usually best.

In 32-bit code, making a stack frame in hand-written code often makes more sense, esp. for maintainability. In 64-bit code, it's usually not much of an issue. Although saving/restoring an extra register with an extra pair of push/pop does change the stack layout, which does matter if any args were passed on the stack (e.g. a large struct by value, but write your function to take a const-pointer arg instead!).

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • Very useful, especially tip about efficiency. So in the case of compiling with optimizations(frame pointer omission) one will get slighly bigger code because more bytes are needed to encode rsp addressing? – Bulat M. Sep 17 '16 at 14:44
  • 1
    @BulatM.: sometimes the saving from leaving out 3 or 4 instructions per function will balance it out. That's 1 + 3 bytes of prologue, and 1 + 0 or 3 bytes of epilogue. (optional `mov rsp, rbp`, then `pop rbp`. Or some compilers use LEAVE, which is 1 byte, which is only 3 uops IIRC, so it's a good choice if RSP isn't pointing to the saved RBP value at the end of a function.) – Peter Cordes Sep 17 '16 at 15:19
  • 2
    When you handwrite assembly, the stack pointer can be tricky to keep track off because local variables may not have a fixed offset from the stack pointer. – fuz Sep 17 '16 at 17:11
  • 2
    @FUZxxl: I fully agree. This is not a problem for compilers, but it is for us humans: you must not forget to account for the new RSP values if you push or pop manually, or change RSP to make room for something. Compilers don't forget such things. – Rudy Velthuis Sep 17 '16 at 19:31
  • 1
    @RudyVelthuis: updated my answer to account for the weaknesses of foolish mortals such as ourselves, compared to machines. – Peter Cordes Sep 17 '16 at 19:57