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[28]. 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.
[28] 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.