An ABI is a term you will hear a lot about when working with systems programming. We have talked extensively about API, which are interfaces the programmer sees to your code.
ABI's refer to lower level interfaces which the compiler, operating system and, to some extent, processor, must agree on to communicate together. Below we introduce a number of concepts which are important to understanding ABI considerations.
Endianess
registers or stack?
On many architectures you must call a function through a function descriptor, rather than directly.
For example, on IA64 a function descriptor consists of two components; the address of the function (that being a 64 bit, or 8 byte value) and the address of the global pointer (gp). The ABI specifies that r1 should always contain the gp value for a function. This means that when you call a function, it is the callees job to save their gp value, set r1 to be the new value (from the function descriptor) and then call the function.
This may seem like a strange way to do things, but it has very useful practical implications as you will see in the next chapter about global offset tables. On IA64 an add instruction can only take a maximum 22 bit immediate value[1]. An immediate value is one that is specified directly, rather than in a register (e.g. in add r1 + 100 100 is the immediate value).
You might recognise 22 bits as being able to represent 4194304 bytes, or 4MB. Thus each function can directly offset into an area of memory 4MB big without having to take the penalty of loading any values into a register. If the compiler, linker and loader all agree on what the global pointer is pointing to (as specified in the ABI) performance can be improved by less loading.
[1] | Technically this is because of the way IA64 bundles instructions. Three instructions are put into each bundle, and there is only enough room to keep a 22 bit value to keep the bundle together. |