M68k Application Binary Interface (ABI)
Variants of ABIs
In this document, we’re covering two variants of M68k ABIs:
- Linux/GCC ABI: The default ABI used by GCC on Linux. It is backward-compatible with many legacy compilers’ ABIs, such as Sun PCC. Yet it lacks formal documentations. This is currently the only ABI implemented by M68k LLVM.
- SystemV ABI: The default ABI used by UNIX systems. There is a nice book documenting related details. Most of the contents in this page are also adapted from it.
Data Representations
Scalar Types
Here are the sizes and alignments of all scalar types.
Type | C Type | sizeof | Alignment (bytes) | |
---|---|---|---|---|
GCC ABI | SysV ABI | |||
Integral | char / signed char / unsigned char | 1 | 1 | 1 |
short / signed short / unsigned short | 2 | 2 | 2 | |
int / signed int / unsigned int | 4 | 2 | 4 | |
long / signed long / unsigned long | 4 | 2 | 4 | |
Pointer | any-type * / any function pointer type | 4 | 2 | 4 |
Floating Point | float | 4 | 2 | 4 |
double | 8 | 2 | 8 | |
long double | 16 | 2 | 8 |
The biggest difference between GCC and SysV ABIs is, of course, the alignment. While SysV adopts natural alignment, GCC by default aligns larger integer types on 16-bit boundaries.
Note that by using the -malign-int
flag, which is not enabled by default, GCC aligns int
, long
, float
, double
, and long double
on 32-bit boundaries.
Another GCC-specific trick is the -mshort
flag, which considers int
to be 16 bits.
Aggregate Types
Here are the rules to determine the layout and alignment of an aggregate type (for example, struct
or union
):
- The alignment of an aggregate type is the maximum alignment of its element types.
- Each member will be placed at the lowest offset while respecting to its member type alignment. Internal paddings will be added if needed.
- The size of an aggregate-type instance should be the multiple of its alignment. Tail paddings will be added if needed.
Stack Alignment
There is a difference between GCC and SysV ABI on stack alignment: By default, GCC aligns stack data on 16-bit boundaries, whereas SysV uses 32-bit alignment.
Calling Convention
In this section, we’re going to talk about the standard calling convention used by M68k. It is splitted into three sub-sections: Stack frame layout, passing function arguments, and handling return values.
Stack Frame
The diagram below shows a typical M68k stack frame:
Usually, people use special instructions to setup a new stack frame. For instance, JSR
pushes return address to the stack before jumping to the destination while RTS
helps you to restore the return address upon returning from a function; LINK
and UNLNK
instructions can help you to save and restore the frame pointer.
In addition to special registers like %fp
and %sp
, other register usages also follow a certain convention.
Register Names | Usage |
---|---|
%d0,%d1 %a0,%a1 |
Scratch registers. Caller-save |
%d2 ~ %d7 %a2 ~ %a5 |
Local (variable) registers. Callee-save |
%a6 (%fp) | Frame pointer (if implemented) |
%a7 (%sp) | Stack pointer |
%pc | Program counter |
%ccr | Condition code register |
For floating point unit, which is available after 68040 / 68881, here is a list of floating point register usages:
Register Names | Usage |
---|---|
%fp0, %fp1 | Scratch registers. Caller-save |
%fp2 ~ %fp7 | Local (variable) registers. Callee-save |
%fpcr | Floating point control register |
%fpsr | Floating point status register |
%fpiar | Floating point instruction address register |
Function Arguments
As illustrated in the previous diagram, incoming function arguments are passed by stack. Since arguments are always aligned on 32-bit boundaries, they can be accessed at the following memory address (assuming frame pointer is implemented):
%fp + 8 + N * 4
where N
is the index of the argument. For instance, the first argument is at %fp + 8
and the second is at %fp + 12
.
When passing an aggregate-type object, regardless of their original type alignment, the object will be aligned on 32-bit boundaries.
Return Values
An integral return value is put in %d0
, whereas a pointer return value is put in %a0
.
When it comes to returning an aggregate-type object, the object should be stored in memory and its address will be put in:
- SysV ABI:
%a0
- GCC ABI:
%a1
Whether it’s caller or callee’s responsibility to allocate the space remains unspecified.