9

I've noticed that a lot of calling conventions insist that [e]bx be preserved for the callee.

Now, I can understand why they'd preserve something like [e]sp or [e]bp, since that can mess up the callee's stack. I can also understand why you might want to preserve [e]si or [e]di since that can break the callee's string instructions if they aren't particularly careful.

But [e]bx? What on earth is so important about [e]bx? What makes [e]bx so special that multiple calling conventions insist that it be preserved throughout function calls?

Is there some sort of subtle bug/gotcha that can arise from messing with [e]bx?

Does modifying [e]bx somehow have a greater impact on the callee than modifying [e]dx or [e]cx for instance?

I just don't understand why so many calling conventions single out [e]bx for preservation.

3 Answers3

8

Not all registers make good candidates for preserving:

no (e)ax -- Implicitly used in some instructions; Return value
no (e)dx -- edx:eax is implicity used in cdq, div, mul and in return values

   (e)bx -- generic register, usable in 16-bit addressing modes (base)
   (e)cx -- shift-counts, used in loop, rep

   (e)si -- movs operations, usable in 16-bit addressing modes (index)
   (e)di -- movs operations, usable in 16-bit addressing modes (index)

Must (e)bp -- frame pointer, usable in 16-bit addressing modes (base)
Must (e)sp -- stack pointer, not addressable in 8086 (other than push/pop)

Looking at the table, two registers have good reason to be preserved and two have a reason not to be preserved. accumulator = (e)ax e.g. is the most often used register due to short encoding. SI,DI make a logical register pair -- on REP MOVS and other string operations, both are trashed.

In a half and half callee/caller saving paradigm the discussion would basically go only if bx/cx is preferred over si/di. In other calling conventions, it's just EDX,EAX and ECX that can be trashed.

EBX does have a few obscure implicit uses that are still relevant in modern code (e.g. CMPXGH8B / CMPXGH16B), but it's the least special register in 32/64-bit code.

EBX makes a good choice for a call-preserved register because it's rare that a function will need to save/restore EBX because they need EBX specifically, and not just any non-volatile register. As Brett Hale's answer points out, it makes EBX a great choice for the global offset table (GOT) pointer in ABIs that need one.

In 16-bit mode, addressing modes were limited to (any subset of) [BP|BX + DI|SI + disp8/disp16]), so BX is definitely special there.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Aki Suihkonen
  • 19,144
  • 1
  • 36
  • 57
  • Some compilers (like Microsoft) have an option to disable frame pointers, in which case ebp becomes a spare register, but I don't know if it's required to be saved by the callee. – rcgldr Mar 06 '14 at 08:30
  • That's true. But when compiling for debug (or in legacy targets), allowing subroutines to trash bp at will would cause severe penalties. The better programming paradigm is to save bp at entry and restore it at exit, instead of save it always before calling and restore it after. – Aki Suihkonen Mar 06 '14 at 10:40
  • As mentioned, eax, ecx, and edx are caller saved (if needed), the rest of the registers, including ebp, would be callee saved or not modified. I was only noting that without frame pointers, after a push ebp, the usual mov ebp,esp would not be needed. – rcgldr Mar 06 '14 at 15:02
  • For ECX, the more important / frequent use is as the shift count for variable-count shifts, which explains the ABI design choice to make it "volatile". rep-string instructions need EDI/ESI as well as ECX, and [LOOP is very slow on so many CPUs that it's never used by compilers](http://stackoverflow.com/questions/35742570/why-is-the-loop-instruction-slow-couldnt-intel-have-implemented-it-efficiently). – Peter Cordes Oct 03 '16 at 10:27
  • 1
    @PeterCordes Your edit implies that BX was preserved in 16-bit calling conventions but this isn't actually true. See my comment to Igor Skochinsky in Brett Hale's answer. – Ross Ridge Oct 03 '16 at 15:35
  • @RossRidge: Thanks, I wrote that based on the assumption that Igor was right. I don't know anything about 16-bit calling conventions! I should maybe put my theory of why EBX makes a good call-preserved register into my own answer instead of trying to improve this one, since it's getting into opinion not just fact. But I'll leave it for now, since Aki's still active on SO and can review the changes I made. – Peter Cordes Oct 03 '16 at 15:45
5

This is a compromise between not saving any of the registers and saving them all. Either saving none, or saving all, could have been proposed, but either extreme leads to inefficiencies caused by copying the contents to memory (the stack). Choosing to allow some registers to be preserved and some not, reduces the average cost of a function call.

Dwayne Towell
  • 8,154
  • 4
  • 36
  • 49
  • I can understand that, but why [e]bx specifically? Wouldn't a register like [e]dx have served just as well for that compromise? – thebennybox Mar 06 '14 at 03:40
  • 4
    Perhaps coincidence, but bx, si, di and bp are the registers that can be used to form an effective address in 16-bit code. – Frank Kotler Mar 06 '14 at 04:11
  • 3
    @FrankKotler tend to agree on that. The original indirect addressing only allowed for `mov tgt, [ base + index + off ]` with `base` being `bx`/`bp` (hence the '`b`') and `index` `si`/`di` (hence the '`i`'). So it seems preserving all regs involved in addressing/stack access (that'd then be `BX`, `BP`, `SP`, `SI` and `DI`) was desirable enough. If you think of `BP`/`SP` as "reserved", then this just neatly splits the general reg set in half, `AX`,`CX`,`DX` being callee-owned and `BX`,`SI`,`DI` being caller-owned. – FrankH. Mar 06 '14 at 13:25
  • 2
    I agree and understand about ebx, but I wrote a lot of 16-bit code that was called from C and we never preserved bx, back in the day. And the move to 32-bit mostly eliminated the distinction between the ebx and edx, so it seems more arbitrary than planned to me. (I am willing to be enlightened.) – Dwayne Towell Mar 08 '14 at 02:35
5

One of the main reasons, certainly for the i386 ELF ABI, is that ebx holds the address of the global offset table (GOT) register for position-independent code (PIC). See 3-35 of the specification for the details. It would be disruptive in the extreme, if, say, shared library code had to restore the GOT after every function call return.

Brett Hale
  • 21,653
  • 2
  • 61
  • 90
  • 2
    The tradition of saving of `bx` certainly predates that - it was done by DOS C compilers, and the Win16/Win32 ABI afterwards. – Igor Skochinsky Mar 06 '14 at 11:49
  • EBX is the most-general-purpose of the integer registers, so even if it wasn't for that tradition, it would be the best choice. (EBP would make a good choice too, if you didn't care about frame pointers.) So this is part of the reason it's still call-preserved in the SysV i386 ABI. I edited Aki's answer to include this point. – Peter Cordes Oct 03 '16 at 11:03
  • 3
    @IgorSkochinsky No, most 16-bit calling conventions, including those used by MS-DOS C compilers, like Microsoft's and Borland's didn't preserve BX across function calls. A few 16-bit calling conventions used it as register parameter and/or a return value for pointers. – Ross Ridge Oct 03 '16 at 15:29