-
Notifications
You must be signed in to change notification settings - Fork 0
Phase 17 library access
Part of the Amiga port design log.
Step 7 (callback thunks) is deferred — see below.
Generic library-call mechanism. Any function in any library — system or third-party — callable from Python with no port-side C per library.
- Every library function lives at a negative offset (LVO) from the base.
- Args go in a fixed set of D/A registers — never on the stack.
- Return value always in D0. Library base in A6.
- All values 32-bit; no struct-in-register, no varargs.
NDK ships a .fd (Function Definition) file per library at
/opt/amiga/m68k-amigaos/ndk-include/fd/*.fd — enough to mechanically look
up call signatures.
Layer 1 — low-level trampoline. ports/amiga/amiga_lib_call.S is a
single hand-written 68k routine. C entry:
uint32_t amiga_lib_call_asm(
uint32_t base, int32_t offset,
uint32_t d0..d7, uint32_t a0..a5);Saves callee-saved set (d2-d7/a2-a6), loads A6 with the base, loads the
14 register slots from the stack. The jump to base + offset uses an
RTS-trick rather than computed JSR: with all D/A regs committed to
user values, no scratch register is free for the call target. Pushing
target + local return label and issuing rts transfers to the library;
the library's own rts pops the local label and returns to our cleanup.
C extension is registered as _amiga; three entry points:
amiga.lib_open(name, version=0) # OpenLibrary; OSError(ENOENT) on fail
amiga.lib_close(base) # CloseLibrary; tolerates 0 base
amiga.lib_call(base, offset, **regs, ret="d0")Register kwargs: d0–d7, a0–a5 (A6 is the base; A7 is SP).
ret="d0" signed, "d0u" unsigned, "void" returns None.
Layer 2 — .fd-driven proxy. Frozen amiga.py re-exports everything
from _amiga, adds a Library class on top of the FD table baked in from
_amiga_fd.py:
with amiga.library("intuition.library", 37) as intuition:
intuition.DisplayBeep(0)
win = intuition.OpenWindow(nw_ptr)
intuition.CloseWindow(win)Library.__getattr__ looks up the signature, builds a closure that maps
positional args into the right register kwargs of _amiga.lib_call, and
setattrs the closure onto the instance so subsequent reads skip
__getattr__. Context-manager support; explicit close(); GC-time
__del__ cleanup. Errors: unknown library → OSError(ENOENT); missing
function → AttributeError; wrong arg count → TypeError; call after
close → ValueError.
tools/amiga-fdgen.py parses every .fd under one or more NDK trees and
emits a Python module whose LIBRARIES dict maps each openable name to
{function_name: (lvo, regs_csv, since)}.
-
Chronological ordering. Hyperion's 3.1.4 → 3.2 → 3.2.x are
calendar-newer than 3.5/3.9 (development restarted years after 3.9).
Hand-maintained
AMIGA_OS_RELEASE_ORDERlist avoids3.2 < 3.9numeric sort wrecking thesincestamps. - Drift detection. LVOs are append-only; a function with a different offset/register list in a later NDK is a hard warning. Earlier entry wins.
-
Public-only.
##privatesections still consume LVO slots (so offsets are correct) but functions are dropped unless--include-private. -
CIA and
mathieeedoub*exceptions don't fit the convention; the tool warns and skips them.
Against current bebbo NDK: 76 .fd files → 75 openable names → 1146 public function signatures, ~80 KB Python source.
When Library(name) doesn't find name in the frozen table, it falls back
to a pure-Python .fd parser in amiga.py and walks PROGDIR:fd/ then
LIBS:fd/, trying both bebbo <base>_lib.fd and bare <base>.fd.
Alternatively, Library(name, version, signatures=...) or
amiga.library_from_signatures(name, version, sigs) bypasses both lookups.
Tag lists are pervasive in OS3.x. Three pieces in modamiga.c:
- Memory primitives:
peek_b/w/l/bytes(addr[, n]),poke_b/w/l/bytes(addr, v). -
TagListclass wrapping anAllocVec'dTagItem[].(tag, value)slots are 8 bytes; ints go straight intoti_Data;bytes/strget their ownAllocVec'd NUL-terminated buffer with the address stored inti_Data. All allocations released byclose()/__exit__/ GC__del__. Defines__int__returning the head address. -
amiga.taglist(...)factory. Kwargs resolved against_amiga_tags.TAGS; positional args support(tag, value)pairs, iterable, or alternatingtag, value, tag, value. Unknown tag names raiseKeyError. -
Libraryproxy runsint(v)on any non-int arg soTagListpasses through cleanly:
with amiga.taglist(WA_Width=640, WA_Height=480, WA_Title="hi") as tags:
with amiga.library("intuition.library", 37) as intuition:
intuition.OpenWindowTagList(0, tags)tools/amiga-taggen.py drives bebbo's m68k-gcc to harvest tag IDs from
NDK headers (preprocess with -E -dM, compile as const ULONG _x_<NAME> = (ULONG)(<NAME>); with -S -O2, parse .long from the assembly). Keeps
only TAG_USER-namespace values (bit 31 set) plus universal TAG_* markers.
1072 tag IDs in _amiga_tags.py, ~48 KB frozen.
amiga.parse_taglist(addr, max_items=64) walks a TagItem array back into a
{tag_id: value} dict, following TAG_MORE chains, dropping TAG_IGNORE,
honouring TAG_SKIP. taglist(..., more=other) writes a TAG_MORE
terminator pointing at other.addr and pins it alive on the outer.
amiga.Struct(addr, layout, name=None) wraps a C struct at a fixed address.
layout is {field_name: (offset, type_code)} with codes B/b/H/h/L/l/P/sN/S.
Defines __int__ so an instance passes straight to a Library call.
Starter layouts shipped in _amiga_structs.py:
| factory | struct |
|---|---|
amiga.Node(addr) |
struct Node |
amiga.Task(addr) |
struct Task |
amiga.Library_struct(addr) |
struct Library (trailing _struct to avoid clash with the proxy class) |
amiga.DateStamp(addr) |
struct DateStamp |
amiga.FileInfoBlock(addr) |
struct FileInfoBlock |
amiga.IntuiMessage(addr) |
struct IntuiMessage |
Callback thunks for hook-driven library calls (CreateNewProc, blit hooks,
ARexx command dispatchers). Needs executable memory and per-callable
thunks. Defer until a concrete user need lands; most OS3.x scripting
doesn't need it.
ports/amiga/manifest.py is freeze("$(PORT_DIR)/modules"). Makefile sets
FROZEN_MANIFEST = manifest.py and MPY_TOOL_FLAGS = -mlongint-impl=longlong
to match MICROPY_LONGINT_IMPL_LONGLONG (else mpy-tool.py emits MPZ
literals which fail the static_assert in frozen_content.c).