Date: | January 2, 2004 / year-entry #2 |
Tags: | history |
Orig Link: | https://blogs.msdn.microsoft.com/oldnewthing/20040102-00/?p=41213 |
Comments: | 27 |
Summary: | The great thing about calling conventions on the x86 platform is that there are so many to choose from! In the 16-bit world, part of the calling convention was fixed by the instruction set: The BP register defaults to the SS selector, whereas the other registers default to the DS selector. So the BP register... |
The great thing about calling conventions on the x86 platform is that there are so many to choose from! In the 16-bit world, part of the calling convention was fixed by the instruction set: The BP register defaults to the SS selector, whereas the other registers default to the DS selector. So the BP register was necessarily the register used for accessing stack-based parameters. The registers for return values were also chosen automatically by the instruction set. The AX register acted as the accumulator and therefore was the obvious choice for passing the return value. The 8086 instruction set also has special instructions which treat the DX:AX pair as a single 32-bit value, so that was the obvious choice to be the register pair used to return 32-bit values. That left SI, DI, BX and CX. (Terminology note: Registers that do not need to be preserved across a function call are often called "scratch".) When deciding which registers should be preserved by a calling convention, you need to balance the needs of the caller against the needs of the callee. The caller would prefer that all registers be preserved, since that removes the need for the caller to worry about saving/restoring the value across a call. The callee would prefer that no registers be preserved, since that removes the need to save the value on entry and restore it on exit. If you require too few registers to be preserved, then callers become filled with register save/restore code. But if you require too many registers to be preserved, then callees become obligated to save and restore registers that the caller might not have really cared about. This is particularly important for leaf functions (functions that do not call any other functions). The non-uniformity of the x86 instruction set was also a contributing factor. The CX register could not be used to access memory, so you wanted to have some register other than CX be scratch, so that a leaf function can at least access memory without having to preserve any registers. So BX was chosen to be scratch, leaving SI and DI as preserved. So here's the rundown of 16-bit calling conventions:
Okay, those are the 16-bit calling conventions I remember. Part 2 will discuss 32-bit calling conventions, if I ever get around to writing it. |
Comments (27)
Comments are closed. |
Just curious, you did this off the top of your head? I have a sheet pinned on the wall of my cube because I soemtime need to do manual stack walks in WinDBG and it’s handy but I can’t ever remember it exactly for more than a week…
I just realised exactly how long it’s been since I wrote in Assembler !
x86 (to be really fair I should say IA-32 / IA-16) show once again why it SHOULD have been regulated to the scrap heap long ago:) 3 or 4 open registers is not enough for a high level language to use to do much of anything without some stack walking. Cache of course helps here (it helps a lot actually, since the stack probably displays more locality of reference than most anything in your application), but the performance would be better with more registers.
Most CPUs developed these days have at least 32 registers (MIPS and SPARC have 32; Itaninums have 128, etc.)
Luckly, AMD has upped the number of registers in their x86-64 assembler (I think they add 8 or 16 off the top of my head). Would that help to make __fastcall live up to its name?
Does/will visual studio support x86-64? If so, does it take advantage of the larger number of registers?
Weren’t there also some fun stuff with the stack pointer sometimes being odd and sometimes even in WIN16?
Does the AMD64 windows calling conventions use a red zone?
register spills didn’t have to be expensive on WIN16 on old CPUs, did they? Couldn’t you occassionally use a segment register to spill to? (At least in real mode…)
Steve: Yes, this was from memory. (And I may have remembered __fastcall incorrectly but the basic idea is sound.)
Eric: I will talk about the ia64 and amd64 calling conventions in a future entry. They are quite interesting.
Peter: Egads I thought everybody had forgotten the odd/even rule. I’ll make another blog entry on that piece of Windows trivia. I don’t know what you mean by a "red zone" though. And although you could spill to a segment register, that was legal only in real mode, which was woefully ancient even in Windows 3.1 days.
http://www.x86-64.org/lists/discuss/msg00122.html
it is an area "above" the stack pointer that the hardware/os/run-time library promise not to touch so leaf functions can store stuff there without having to adjust the stack pointer.
It is hinted at (not by name) in the section "A five-per-cent Digression" of "Computer Science Technical Report No. 102: The C Calling Sequence" by Johnson and Ritchie, Bell Labs, Sep. 1981.
I don’t know when or how it got the name "red zone".
Note that MS __fastcall and the Borland __fastcall are also different conventions that by some twist of history share the same name. (The quoted article refers to Borland __fastcall, BTW)
I’m not familiar enough with the history of MS compilers to know where that convention came from, but for Borland __fastcall was born as ‘the register calling convention’ that is the standard convention used by the Delphi language (their object-oriented Pascal variant) This used 3 registers to pass the first 3 register-sized values (integers, pointers, etc) Your return value also counts as one of these 3, as does the implicit ‘self’ pointer for object method calls. The big advantage was supposed to be for functions passing entirely in registers ( <= 3 params, all register sized) where the traditional prolog/epilog would be omitted.
When Borland introduced a C++ mapping to their Delphi class library with C++Builder, they needed a keyword for this convention and as register was already taken by the language they chose __fastcall.
Just to make things more fun for the poor Borland user, after MS introduced __fastcall they had to have some way to maintain compatibility with code exporting this convention out of DLLs, and as they already used __fastcall themselves, __msfastcall was introduced! This might also mean doctoring yuor supplied headers to use the correct spelling for fastcall <g>
This all occurred in the Win32 era though
The Borland __fastcall convention always seemed more logical and more efficient to me than the MS fastcall. It uses eax, edx, and ecx to pass parameters, where the MS convention only uses two of these (forgotten which ones). All other registers are always preserved. This applies to Win32.
Hi Raymond, more questions :D Is is true that Win16 used pascal calling convention because it was actually written in pascal?
Mike: No, the reason Win16 used the pascal calling convention was the reason I stated in my entry. Win16 was written in a mix of assembly and C. (KERNEL was almost entirely assembly; GDI was largely assembly, and USER was mostly C.)
Remember that Pascal doesn’t support casting, which would have made things like WndProcs pretty close to impossible to write in Pascal.
Many Pascal dialects do/did support casting. All the ones I have used did. It is pretty easy to add to the language so that excuse doesn’t count ;)
Mike, I think you are thinking of the Macintosh. As far as I know the toolbox ROMs were mostly written in assembler but they did choose Pascal as their systems programming language (for non-ROM stuff). The original Inside the Macintosh shows Pascal "prototypes" for (almost?) everything.
Very cool 8)
I believe fastcall uses cx for the first and dx for the 2nd. At least that makes sense since 32 bit is ecx and edx for the 1st and 2nd respectively.
I was told (I believe by Charles Petzold) that Win16 used the Pascal calling convention because it allowed Windows 1.0 to ship using one fewer install floppies. (Which, back then, was a big deal)
While reading these 16-bit details, I suddenly had a "flash back" to the old Win16 days — MakeProcInstance(), movable memory, shared DLLs, and other horrors. Ack! Pfft!!
??????,??????????? ?????????? calling convention: The history of calling conventions, part 1 The history of calling conventions, part 2 The history of calling conventions, part 3 The history of calling conventions, part 4: ia64 Why do member functions need to be…
Any chance of explaining AMD64 fastcall convention sported in windows server 2003 ddk compiler? I have checked a listing and it seems to pass first 2 integers in ecx/edx then first 2 floats/doubles in xmm2/xmm3 and then the rest on stack above [rsp + 32]. Can anyone confirm this or point me to some docs?
Um, I already did: http://weblogs.asp.net/oldnewthing/archive/2004/01/14/58579.aspx
The history of calling conventions
PingBack from http://www.hamo.cn/2007/06/dive-into-passcal-call/419.html