Comprehensive technical documentation covering: - Hardware: OCS/ECS/AGA custom chip registers, Copper & Blitter deep dives - Boot sequence: cold boot through startup-sequence - Binary format: HUNK executable spec, relocation, debug info - Linking & ABI: .fd files, LVO tables, register calling conventions - Exec kernel: tasks, interrupts, memory, signals, semaphores - AmigaDOS: file I/O, FFS/OFS layout, CLI/Shell scripting - Graphics: planar bitmaps, Copper programming, HAM/EHB modes - Intuition: screens, windows, IDCMP, BOOPSI - Devices: trackdisk, SCSI, serial, timer, audio, keyboard - Libraries: utility, expansion, IFFParse, locale, ARexx - Networking: bsdsocket API, SANA-II, TCP/IP stack comparison - Toolchain: GCC, vasm/vlink, SAS/C, NDK, debugging - Reverse engineering: IDA/Ghidra setup, compiler fingerprints, case studies - CPU & MMU: 68040/060 emulation libs, PMMU, cache management - Driver development: SANA-II, Picasso96/RTG, AHI audio All files include breadcrumb navigation. No local paths or proprietary content.
5.3 KiB
Compiler Inline Stubs
Overview
AmigaOS library functions are not called via the C standard ABI. Every call goes through a negative-offset JMP table relative to a library base pointer held in an address register (conventionally A6). Compiler vendors each solve this differently:
- SAS/C —
#pragmaheaders andinline/headers - GCC (m68k-amigaos) — inline-assembly
__attribute__((regparm))stubs - VBCC —
__reg()storage class and module pragmas
All approaches ultimately compile to the same machine code:
MOVEA.L _DOSBase, A6
JSR -LVO(A6) ; e.g. JSR -138(A6) for dos.library Output()
SAS/C: Pragma-Based Stubs
Pragma Syntax
SAS/C uses #pragma directives to declare register assignments:
#pragma amicall(DOSBase, 0x008A, Open(D1, D2))
DOSBase— global holding the library base (placed in A6 automatically)0x008A— LVO offset (hex)Open(D1, D2)— function name and register allocation for arguments
Include Structure (NDK39)
NDK39/
include/
pragmas/
dos_pragmas.h ← #pragma amicall directives for dos.library
exec_pragmas.h ← exec.library pragmas
graphics_pragmas.h
...
inline/
dos.h ← Alternative: inline-macro stubs (SAS/C 6.x)
Usage:
#include <clib/dos_protos.h> /* ANSI prototypes */
#include <pragmas/dos_pragmas.h> /* register pragmas */
extern struct DosLibrary *DOSBase;
BPTR fh = Open("foo", MODE_OLDFILE); /* expands to JSR -30(A6) */
Pragma-Generated Code
; Open("foo", MODE_OLDFILE)
MOVE.L #MODE_OLDFILE, D2
MOVE.L #_str_foo, D1
MOVEA.L _DOSBase, A6
JSR -30(A6)
; Return value in D0
No stack frame involved — pure register passing.
GCC (m68k-amigaos): Inline Assembly Stubs
Modern Approach (GCC 6.x+ / bebbo cross-compiler)
The NDK provides <proto/dos.h> which pulls in compiler-specific stubs. Under bebbo GCC:
/* auto-generated stub in clib2 / libnix */
static inline BPTR __attribute__((always_inline))
Open(CONST_STRPTR name, LONG accessMode)
{
register BPTR __ret __asm("d0");
register struct DosLibrary *const __DOSBase __asm("a6") = DOSBase;
register CONST_STRPTR __name __asm("d1") = name;
register LONG __accessMode __asm("d2") = accessMode;
__asm volatile ("jsr %%a6@(-30:W)"
: "=r"(__ret)
: "r"(__DOSBase), "r"(__name), "r"(__accessMode)
: "d1", "a0", "a1", "fp0", "fp1", "cc", "memory");
return __ret;
}
Key points:
__asm("a6")forces the library base into A6__asm("d1"),__asm("d2")— per-function register assignments from.fdfile"jsr %%a6@(-30:W)"— 16-bit signed displacement form (most efficient)- Clobbers declared explicitly to prevent register allocation conflicts
Older GCC (< 6.x): __OLDSTYLE__ macros
#define Open(name, mode) \
({ BPTR _r; \
__asm volatile ("movea.l %2,a6; jsr -30(a6)" : "=d"(_r) : \
"d"(name), "d"(mode), "m"(DOSBase) : "a6"); \
_r; })
Less elegant — explicitly moves DOSBase to A6 inside the macro.
VBCC: __reg() Storage Class
VBCC uses a compiler extension to place variables in specific registers:
/* vbcc style */
BPTR __reg("d0") Open(__reg("d1") CONST_STRPTR name,
__reg("d2") LONG mode);
#pragma amicall(DOSBase, 0x1E, Open(d1,d2))
VBCC automatically inserts MOVEA.L DOSBase,A6 and JSR -30(A6).
Module pragma file (dos.fd-derived):
##base DOSBase
##bias 30
Open(name,accessMode)(d1,d2)
##bias 36
Close(file)(d1)
...
Generated Machine Code (All Compilers)
For Write(fh, buf, len) at LVO −48 ($30):
MOVEA.L _DOSBase, A6 ; load library base into A6
MOVE.L fh_var, D1 ; BPTR filehandle → D1
MOVE.L buf_ptr, D2 ; buffer address → D2
MOVE.L len_val, D3 ; byte count → D3
JSR -48(A6) ; call Write()
; D0 = bytes actually written, or -1 on error
Register Allocation Convention
All AmigaOS library functions follow this invariant:
| Register | Role |
|---|---|
| A6 | Library base (always) |
| D0 | Return value (32-bit) |
| D0+D1 | 64-bit return (rare: DivideU) |
| D1–D7, A0–A3 | Arguments (per .fd file) |
| A4, A5 | Scratch in OS (do not rely on preservation) |
| D2–D7, A2–A3 | Preserved by OS calls (callee-saved) |
A compiler stub must:
- Load arguments into exact registers from the
.fdspecification - Load the library base into A6
- Execute
JSR -LVO(A6) - Collect the return value from D0
Stub Generation Tools
| Tool | Input | Output |
|---|---|---|
fd2pragma (SAS) |
.fd file |
#pragma amicall header |
fd2inline |
.fd file |
GCC inline-asm header |
fd2sfd |
.fd file |
SFD (Amiga DevKit) format |
cvinclude.pl |
.fd + .sfd |
VBCC pragma headers |
NDK39 ships pre-generated pragmas/ and inline/ directories — you only need to run these tools when writing a new library.
References
- NDK39:
include/pragmas/,include/inline/,fd/ - SAS/C 6.x Programmer's Reference Manual — chapter on pragmas
- GCC for Amiga (bebbo):
m68k-amigaos-gccrepo,libnixstubs - VBCC manual: http://www.compilers.de/vbcc.html — register specification chapter
- vlink documentation: http://sun.hasenbraten.de/vlink/