Hmm lemme see here... yeah they're all using BP for stack frame. I gave that aspect as example of what level of generality I was thinking of.
Ah, okay. Yes, that is a result of the 80x86 instruction set and register architecture (see e.g
ref.x86asm.net).
The idiom is so deep that Intel later added the instructions ENTER and LEAVE to construct and tear down the stack frame using EBP and ESP exact that way.
Basically, the sum-of-two registers indirect addressing mode only supports four register combinations:
[BX+SI],
[BX+DI],
[BP+SI], and
[BP+DI]. BX register is one of the four that is split into two sub-register halves, BL and BH. SI and DI have auto-incrementing addressing modes, and are used for other indirect addressing modes. That basically leaves BP for the stack frame. (SP being the stack pointer itself.)
However, GCC for example does support
-fomit-frame-pointer, and Intel Compiler Collection supports
/Oy, which both omit reserving BP for the stack frame, if at all possible.
Is... is it not actually useful to speak of "ABI"s in such general terms?
Welp, I don't usually go into the details, no; they're not that useful, really. The differences on x86 are quite small, anyway.
On x86-64, the calling conventions differ significantly. In particular, MS calling convention places the
four first integer or pointer parameters in registers, with up to four XMM registers for floating-point and SIMD parameters. SYSV places the
six first integer or pointer parameters in registers, and up to eight XMM registers. Any additional parameters will be put on the stack, which for very "hot" functions, can cause a notable slowdown.
(In other words, that for portable efficient x86-64 code, one prefers to keep the number of integer or pointer parameters per C call to four or less, plus up to four floating-point or xmm parameters; but for non-Windows C code, up to six plus eight is fine. That's about it. I'd need to re-check for C++, but I typically just assume the standard library uses a hidden pointer for the object, but otherwise uses a similar calling convention.)
Well yeah, like I said, about three?
I mostly pointed out those because it is a whole other culture (Unix, VxWorks, and the much rarer embedded folks) that wasn't Microsoft on x86, and those who did use DOS or Windows way back when, often erroneously think that that was all that 80x86 was used for. No, there was a LOT more.
Moreover, there was a lot of standardization efforts outside the Microsoft enclave, especially
Single UNIX Specification that became
POSIX (IEEE Std 1003.1).
Or did they use soft INT calls like MS-DOS too?
That varies between the ABIs/calling conventions. On the 8086, 80186-80486, the
int instruction is basically the easiest way to do a "syscall".
x86-64 (AMD64 instruction set) provides a separate
syscall assembly instruction.
In the MS-DOS era, it was common for each compiler to have their own calling convention; there wasn't an ABI per se, except maybe for BIOS and such.
One of my first proper paid programming gigs in the early-mid nineties was to copy-protect a commercial MS-DOS program, so that each copy was different, and contained a difficult-to-replace version identifier. A key part of that was that I replaced the EXE relocator code with my own hand-written one, which modified (not frobnicated, but close) basically all of the code, and failed if the key in the file itself did not match what the relocator code incidentally produced. (Basically, each serial number was then a random number.) The funky part of that job was that I didn't get any access to the source code (talk about paranoid people!), and I actually wrote most of the assembly code using MS-DOS
debug.exe.
In other words, even the "run-time linker" itself was provided by the compiler, and not the OS.
It is important to note that the x86 BIOS, too, became much more complex when
ACPI came around. It is nothing like "load some registers and then run
int 0xHH anymore.
Maybe that's not common enough to share ABIs, I don't know.
Actually, the commonalities were more due to a desire for compatibility, especially in the C compilers. The
86open (that started in 1997) is what brought Linux so close to Unix. Their work completed in 1999, when the
ELF file format was chosen for executables and dynamic libraries across most operating systems running on x86. Even Intel participated, and started efforts that eventually made their Intel Compiler Collection compatible enough with GCC to be used in SYSV ABI OSes on x86. At and after the turn of the century, if the OSes used the same calling convention, you could run binaries for one operating system on a completely different operating system, using a simple "shim" layer in between!
How many of you have heard of
Open64? It is the GPL-licensed C compiler AMD provided for the new AMD64 (x86-64) instruction set, and what Nvidia used to optimize code in their CUDA toolchain. It continued the standardization path that bridged different companies and open source projects in the nineties on x86, and in some ways culminated in ISO C99 and POSIX.1-2001 (IEEE Std. 1003-2001). Around that time, Microsoft saw the existing standardization efforts as their opponent, and did all sorts of nefarious stuff, that basically stopped any further cross-platform standardization for well over a decade. (Even their brightest star, the Annex K "safe" bounds-checking interfaces (the ones with the
_s suffix you see in Microsoft documentation, is being discussed as to-be-removed in a future version of the C standard, as there isn't any real use for them.)
In a very real sense, a lot of positive momentum –– not towards a
single goal, but towards
interoperability and modularity and freedom of choice –– was lost soon around the turn of the century. We might be at the point where we have most of that back, but I'm not sure (as in I do not know; I do not have enough information to be sure one way or another).
Even GCC development stagnated horribly, becoming a cult-like gathering of "if you don't have a PhD in Computer Science, we shall not read your messages, you peon" self-aggrandizing idiots. (I believe it was a big reason why AMD supported Open64 so much more than GCC.) I guess it was the appearance of LLVM and Clang, and a new generation of developers, that managed an internal change in GCC. It's still a slow and cumbersome to participate in, but it's definitely better than it was at some point! And GCC is significant, because not only does it support a huge swath of different hardware, but it has quite a few language backends, too.
So, yeah; many of the choices done were compromises, but they were done for a good reason, to achieve at least the possibility of interoperability across operating systems on x86.