amiga-bootcamp/04_linking_and_libraries/register_conventions.md
Ilia Sharin 7df1f11f15 03/04: deep enrichment of loader/exec format and linking/libraries
Sections 03 and 04 augmented to bootcamp quality with targeted
enrichment based on content analysis (not just file size).

03_loader_and_exec_format:
- overlay_system.md: full rewrite — tree architecture diagram,
  HUNK_OVERLAY binary format, overlay manager runtime internals,
  worked binary example, linker support, modern alternatives
- hunk_relocation.md: full rewrite — visual before/after diagram,
  patching algorithm with code, RELOC32SHORT and DREL32 formats,
  PC-relative impact comparison table, self-referencing relocs,
  error scenarios, Python reloc scanner tool

04_linking_and_libraries:
- library_structure.md: full rewrite — ASCII memory layout diagram,
  JMP table encoding (why 6 bytes), MakeLibrary internals with both
  function array formats, complete library creation example with
  .fd file, checksum verification, lifecycle state diagram
- shared_libraries_runtime.md: full rewrite — OpenLibrary 4-step
  resolution path, ramlib disk loader internals, disk search path,
  version negotiation table (v33-v47), CloseLibrary/Expunge deep
  dive, memory-low sweep, common pitfalls table
- register_conventions.md: full rewrite — FPU register map,
  inter-library A6 save/restore pattern, small-data model with
  __saveds keyword, varargs/TagItem pattern deep dive,
  stack-based wrapper explanation, disassembly identification

Updated indexes:
- 03_loader_and_exec_format/README.md
- 04_linking_and_libraries/README.md
- Root README.md (sections 03 and 04)
2026-04-23 18:30:45 -04:00

315 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[← Home](../README.md) · [Linking & Libraries](README.md)
# M68k Register Calling Conventions on Amiga
## Overview
AmigaOS uses a **pure register-based calling convention** for all OS API calls. There is no stack-based C ABI for library functions. Every argument is passed in a specific CPU register defined by the `.fd` file for that library. This document covers the complete convention including integer, FPU, varargs, tag-based calls, small-data model, and inter-library patterns.
---
## The AmigaOS Register Convention
### Integer Registers
| Register | Role | Preserved? |
|---|---|---|
| **A6** | Library base pointer (always) | **No** — overwritten with target lib |
| **D0** | Return value (32-bit) | **No** — scratch |
| **D1** | 2nd return (64-bit pair) or argument | **No** — scratch |
| **A0** | Argument or scratch | **No** — scratch |
| **A1** | Argument or scratch | **No** — scratch |
| D2D7 | Arguments (per .fd) | **Yes** — callee-preserved |
| A2A3 | Arguments (per .fd) | **Yes** — callee-preserved |
| A4 | Small-data base pointer (VBCC/GCC) | **Yes** — callee-preserved |
| A5 | Frame pointer (SAS/C) or scratch | **Yes** — callee-preserved |
| A7 (SP) | Stack pointer | Maintained by convention |
### Key Rules
1. **A6 is always destroyed** — it holds the target library base before and after every OS call
2. **D0, D1, A0, A1** are volatile — the OS may destroy them in any library call
3. Callee-preserved means the OS function saves and restores these registers internally
4. Arguments go in the registers specified by the `.fd` file — never on the stack
### FPU Registers (68881/68882/68040/68060)
| Register | Role | Preserved? |
|---|---|---|
| **FP0** | Return value (floating-point) | **No** — scratch |
| **FP1** | Scratch | **No** — scratch |
| FP2FP7 | Available for arguments | **Yes** — callee-preserved |
> **Note**: Very few AmigaOS functions use FPU registers. `mathieeedoubtrans.library` and `mathtrans.library` pass arguments via D0/D1 (as 32-bit or 64-bit integer representations of IEEE floats), not in FP registers. Only custom libraries designed for FPU-equipped systems use FP0FP7 for parameter passing.
---
## Example: dos.library Write()
From `fd/dos_lib.fd`:
```
Write(file,buffer,length)(d1,d2,d3)
```
```asm
; Assembly — calling Write(fh, buf, 512):
MOVEA.L _DOSBase, A6 ; Library base → A6
MOVE.L fh, D1 ; BPTR filehandle → D1
MOVE.L buf, D2 ; Buffer address → D2
MOVE.L #512, D3 ; Byte count → D3
JSR -48(A6) ; Write() LVO = -48
; D0 = bytes written (-1 = error)
; D1, A0, A1, A6 are now UNDEFINED
; D2, D3 still hold their values (callee-preserved)
```
```c
/* C — compiler generates the above automatically */
LONG n = Write(fh, buf, 512);
```
---
## Register Preservation Map
```
After ANY OS library call:
┌─────────────────────────────┐
│ DESTROYED (don't rely on): │
│ D0 D1 A0 A1 A6 │
│ FP0 FP1 │
│ CCR (condition codes) │
└─────────────────────────────┘
┌─────────────────────────────┐
│ PRESERVED (guaranteed): │
│ D2 D3 D4 D5 D6 D7 │
│ A2 A3 A4 A5 │
│ FP2 FP3 FP4 FP5 │
│ FP6 FP7 │
│ SP (A7) │
└─────────────────────────────┘
```
This matches the Motorola 68000 family software convention. AmigaOS does **not** follow the System V m68k ABI (which uses stack-based parameter passing).
---
## Inter-Library Calls
When one library function calls another library internally, it must save/restore A6:
```asm
; Inside dos.library, calling exec.library AllocMem:
MOVEM.L A6, -(SP) ; Save current A6 (dos.library base)
MOVEA.L _SysBase, A6 ; Load exec.library base
MOVE.L #1024, D0 ; byteSize
MOVE.L #$10001, D1 ; MEMF_PUBLIC|MEMF_CLEAR
JSR -198(A6) ; AllocMem()
MOVEA.L (SP)+, A6 ; Restore dos.library base
; D0 = allocated memory or NULL
```
> **Common bug**: Forgetting to save/restore A6 in hand-written assembly library code. The caller expects A6 to point to the library it called, but internally the library swapped A6 to call another library.
---
## Small Data Model (A4-Relative)
C compilers can use A4 as a **base register** for accessing global/static data, reducing code size:
### SAS/C Small Data
```c
/* SAS/C: compile with -b0 (small data model) */
/* Generates A4-relative addressing for globals */
```
```asm
; Without small data:
MOVE.L _myGlobal, D0 ; 6 bytes: absolute addressing
; With small data (A4-relative):
MOVE.L -$1234(A4), D0 ; 4 bytes: A4-relative displacement
```
### __saveds Keyword
The `__saveds` keyword tells the compiler to reload A4 on function entry — essential for library functions and interrupt handlers that may be called from any context:
```c
/* Library function: */
LONG __saveds MyFunc(LONG arg __asm("d0"))
{
/* __saveds generates: */
/* LEA __LinkerDB, A4 ; reload small-data base */
/* ...function code... */
return arg * 2;
}
```
```asm
; What __saveds generates:
_MyFunc:
LEA __LinkerDB, A4 ; Restore small-data base register
; Now A4-relative data access works
MOVE.L D0, D1
ADD.L D1, D0
RTS
```
**When you need `__saveds`:**
- Library functions (called via JMP table from any task)
- Interrupt handlers (`AddIntServer` callbacks)
- Hook functions (Intuition/BOOPSI hooks)
- Callback functions passed to other libraries
**When you DON'T need `__saveds`:**
- Regular functions called within the same program (A4 already set)
- Programs compiled with the large data model (absolute addressing)
### GCC Equivalent
```c
/* GCC: -msmall-code -mbaserel flags */
/* Uses A4 as the small-data base pointer */
/* __saveds equivalent: __attribute__((saveds)) */
LONG __attribute__((saveds)) MyFunc(LONG arg __asm("d0"))
{
return arg * 2;
}
```
---
## Varargs and Tag-Based Calls
### The Problem
The register convention breaks down for variable-argument functions. You can't pass an unknown number of arguments in registers. AmigaOS solves this with **TagItem arrays**:
```c
/* Fixed-register call (standard): */
struct Window *OpenWindow(struct NewWindow *nw __asm("a0"));
/* → A0 = pointer to struct, A6 = IntuitionBase */
/* Tag-based call (varargs): */
struct Window *OpenWindowTagList(
struct NewWindow *nw, /* A0 */
struct TagItem *tagList /* A1 — pointer to tag array */
);
```
### TagItem Structure
```c
struct TagItem {
ULONG ti_Tag; /* tag identifier */
ULONG ti_Data; /* tag value */
};
/* Example tag list: */
struct TagItem tags[] = {
{ WA_Left, 100 },
{ WA_Top, 50 },
{ WA_Width, 640 },
{ WA_Height, 480 },
{ WA_Title, (ULONG)"My Window" },
{ TAG_DONE, 0 }
};
struct Window *win = OpenWindowTagList(NULL, tags);
```
### Stack-Based Varargs Wrappers
For convenience, AmigaOS provides `...Tags()` wrappers that build the tag array on the stack:
```c
/* This is a MACRO or inline function, NOT an OS call: */
struct Window *OpenWindowTags(struct NewWindow *nw, ULONG tag1, ...)
{
/* Tags are pushed onto the stack by the caller */
/* The wrapper takes the address of tag1 and passes it as a TagItem* */
return OpenWindowTagList(nw, (struct TagItem *)&tag1);
}
/* Usage: */
struct Window *win = OpenWindowTags(NULL,
WA_Left, 100,
WA_Top, 50,
WA_Width, 640,
WA_Height, 480,
WA_Title, "My Window",
TAG_DONE);
```
> **Important**: The `...Tags()` functions are **not** OS calls — they are C macros or inline stubs that build a tag array on the caller's stack and pass it to the real `...TagList()` function. They do NOT use the stack for parameter passing to the OS.
### Common Tag Patterns
| Real function (register) | Convenience wrapper (stack) |
|---|---|
| `OpenWindowTagList(nw, tags)` | `OpenWindowTags(nw, ...)` |
| `CreateNewProcTags(tags)` | Doesn't exist — use TagList directly |
| `AllocDosObjectTagList(type, tags)` | `AllocDosObject(type, ...)` |
| `SetAttrsA(obj, tags)` | `SetAttrs(obj, ...)` |
---
## C Compiler Differences
### SAS/C 6.x
- Uses A5 as frame pointer (`LINK A5; UNLK A5`)
- Uses A4 for small data model (opt-in with `-b0`)
- `#pragma amicall` for register specification
- `__saveds` for A4 reload in library/callback functions
### GCC (bebbo m68k-amigaos)
- No frame pointer by default (`-fomit-frame-pointer`)
- Uses A4 for small data (`-mbaserel`, `-msmall-code`)
- Inline assembly with `__asm("register")` constraints
- `__attribute__((saveds))` for A4 reload
### VBCC
- `__reg("d0")` storage class for explicit register placement
- No frame pointer — generates very compact code
- Uses A4 for small data model by default
- `__saveds` supported
---
## Detecting the Convention in Disassembly
### Identifying an OS API Call
```asm
; Pattern 1 — Standard library call:
MOVEA.L (_DOSBase).L, A6 ; Load library base
[move args to registers] ; Per .fd specification
JSR (-138,A6) ; Call at LVO -138
; Pattern 2 — PC-relative base load (GCC):
LEA _DOSBase(PC), A0 ; PC-relative load of base address
MOVEA.L (A0), A6 ; Dereference
JSR (-30,A6) ; Open()
; Pattern 3 — Small-data base load (SAS/C):
MOVEA.L -$42(A4), A6 ; Load base from A4-relative storage
JSR (-48,A6) ; Write()
```
Cross-reference the LVO against the `.fd` file to identify the function. IDA's Amiga loader applies LVO names automatically when library definitions are present.
---
## References
- NDK39: `fd/*.fd` — register assignments per function
- NDK39: `utility/tagitem.h` — TagItem structure
- *Amiga ROM Kernel Reference Manual: Libraries* — register conventions appendix
- SAS/C 6.x Programmer's Guide — calling convention, small data, __saveds
- GCC m68k-amigaos (bebbo) — `libnix` inline headers
- VBCC documentation — register specification chapter