Skip to content

Phases 0 11 foundation

Simon Dick edited this page Jun 20, 2026 · 1 revision

Phases 0–11 — Foundation

Part of the Amiga port design log.

The foundational phases: toolchain, a bootable skeleton, file system and import, the standard library, the amiga C module, the first cut of the 68k native emitter, package imports, Ctrl+C handling, the migration off newlib onto native AmigaOS APIs, networking, command-line parsing, and the CI build. The native-emitter rework that completes Phase 5's work lives on its own page, Phase 12 — 68k native emitter rework.

Phase 0 — Toolchain ✅

bebbo GCC 6.5.0b at /opt/amiga. m68k-amigaos-gcc --version confirms.

Phase 1 — Skeleton ✅

Builds an AmigaOS loadseg()ble HUNK executable; confirmed running in Amiberry.

Notable mpconfigport.h settings worth remembering:

  • Register-based NLR via __m68k__ auto-detect (Phase 12a); mpconfigport.h deliberately does not set MICROPY_NLR_SETJMP
  • MICROPY_LONGINT_IMPL_LONGLONG'q'/'Q' struct codes and >30-bit literals need this; adds ~5 KB.
  • MICROPY_ENABLE_PYSTACK (1)not optional. bebbo gcc 6.5's alloca returns 2-byte-aligned addresses on 68k, which break mp_obj_is_obj's (o & 3) == 0 check on iterator slots; routing through mp_pystack_alloc (4 KB aligned(8) buffer) gives deterministic alignment.

mp_builtin_open_obj must be supplied by the port (py/modio.c references but doesn't define it).

Phase 2 — File System and import

open(), read, write, seek, tell, readline, with, and import from any mounted volume all work. Originally newlib stdio in amigaio.c / amigafile.c; Phase 8 migrated to direct dos.library; Phase 16 then rewrote behind a VFS layer (vfs_amiga.c) — those files are gone.

Phase 3 — Standard Library Modules ✅

MICROPY_CONFIG_ROM_LEVEL_EXTRA_FEATURES. Working: math, struct, json, re, hashlib, float. sys.platform == "amiga".

-msoft-float is in CFLAGS — no hardware FPU on 68020. FPU variants (Phase 27) drop this.

json.loads workaround. Upstream extmod/modjson.c fails on 68k with OSError: stream operation not supported — it builds a stack-allocated mp_obj_stringio_t and routes through the stream protocol, but mp_obj_is_obj() requires 4-byte alignment which the 68k SysV ABI doesn't guarantee for every stack frame. ports/amiga/modjson.c is a port-local replacement that bypasses the stream protocol for loads; Makefile filters out extmod/modjson.c.

Phase 4 — amiga Module ✅

ports/amiga/modamiga.c exposes exec/dos primitives. Must be in SRC_QSTR (not just SRC_C) so MP_REGISTER_MODULE and qstrs are picked up.

import amiga
amiga.os_version()                          # (version, revision)
amiga.find_task(name=None)                  # int task pointer (current if None)
amiga.alloc_vec(size, flags=amiga.MEMF_ANY) # int address; MemoryError on fail
amiga.free_vec(addr)
amiga.execute(cmd)                          # int rc (0/5/10/20, -1=start fail)
amiga.exists(path)                          # bool, suppresses volume requesters
# MEMF_ANY/PUBLIC/CHIP/FAST/CLEAR constants

amiga.execute() uses SystemTagList() (not Execute()) so the real CLI return code surfaces. amiga.exists() brackets Lock with pr_WindowPtr = (APTR)-1 so an unmounted volume doesn't pop an "Insert volume" requester.

Later phases add many more bindings to this module — see their sections.

Phase 5 — 68k Native Code Emitter ✅

@micropython.native / --emit native via GENERIC_ASM_API.

File Role
py/asm68k.h/.c 68k instruction encoder + helpers
py/emit68k.c #define N_68K 1 + #include "py/emitnative.c"

Register allocation: D0 = RET/ARG_1, D1/D2/D3 = ARG_2/3/4 (D2/D3 callee-saved, reloaded in prologue), D4–D6 = temps, D7 = REG_LOCAL_1, A2/A3 = LOCAL_2/3 (used for REG_GENERATOR_STATE / REG_QSTR_TABLE), A4 = REG_FUN_TABLE, A5 = frame ptr. MAX_REGS_FOR_LOCAL_VARS = 1 — only D7 is a data register safe for arithmetic.

Calling convention: AmigaOS/cdecl, args pushed right-to-left. LINK A5,#-N; MOVEM.L D2-D7/A2-A4,-(SP) for entry, branches always .W form, CMP.L; Scc; ANDI.L #1.

Known limitations (tracked as Phase 12):

  • try/except/with in native mode — fixed in Phase 12a via the register-based NLR (ports/amiga/nlr68k.S).
  • Nested functions / closures / generators inside @micropython.nativefixed in Phase 12b (big-endian prelude-index byte order).
  • Viper shifts past bit 15 — fixed in Phase 12c (long-size shift opcodes).
  • MAX_REGS_FOR_LOCAL_VARS = 1 caps viper at one register-held local (the rest spill to the stack). This is a performance limit only — results are correct — and is partly intrinsic to the 68k register layout (only D7 is a data register free for cached locals).

Phase 6 — Package imports ✅

mp_import_stat() uses dos.library Lock/Examine; fib_DirEntryType > 0 is a directory. import mypackage works.

Phase 7 — Ctrl+C interrupt handling ✅

Two paths:

  1. During computation: MICROPY_VM_HOOK_LOOP polls CheckSignal(SIGBREAKF_CTRL_C) every 1024 bytecodes (amiga_check_ctrl_c in mphalport.c).
  2. During input: mp_hal_stdin_rx_chr() checks mp_interrupt_char.

shared/runtime/interrupt_char.c provides mp_interrupt_char and mp_hal_set_interrupt_char().

Phase 8 — Native AmigaOS API migration ✅

All newlib stdio replaced with dos.library:

Component Now uses
Console input FGetC(Input())
Console output Write(Output(), buf, len)
File I/O BPTR + Open/Read/Write/Close/Seek/Flush
Heap AllocVec(MEMF_ANY|MEMF_PUBLIC|MEMF_CLEAR)
GC stack bounds FindTask(NULL)->tc_SPUpper
mp_hal_delay_ms Delay() (later replaced by Phase 23 timer.device)

Phase 9 — Networking ✅

ports/amiga/modsocket.c via bsdsocket.library. SocketBase opened in main() (silently absent if library missing — socket creation raises OSError). Errno() (per-library) instead of global errno; IoctlSocket(FIONBIO); SO_RCVTIMEO/SO_SNDTIMEO; Inet_NtoA/inet_addr/ gethostbyname; getaddrinfo/freeaddrinfo/gethostname. Stream protocol implemented so readline(), with, etc. work. CloseSocket() (not close) on shutdown. __NO_NETINCLUDE_TIMEVAL guard avoids devices/timer.h / sys/time.h conflict.

Phase 10 — Command-line argument parsing ✅

main.c parses argc/argv before the REPL:

micropython                     # interactive REPL
micropython script.py [args]    # sys.argv = ["script.py", ...]
micropython -c "code" [args]    # sys.argv = ["-c", ...]
micropython -m module [args]    # sys.argv = ["module", ...]
micropython -h / --help / --version

MICROPY_PY_SYS_ARGV (1), mp_sys_path = [""]. Script directory is prepended to sys.path[0] using AmigaOS path parsing. -c via pyexec_vstr; -m via mp_builtin___import__. Raw console mode (SetMode(stdin, 1)) is REPL-only. genhdr/mpversion.h included for MICROPY_BANNER_NAME_AND_VERSION.

The bebbo argv parser is broken under vamos with multi-arg invocations; amiga_parse_args parses pr_Arguments itself.

Phase 11 — CI build workflow ✅

.github/workflows/ports_amiga.yml, manually triggered via workflow_dispatch with a ref input (branch / tag / commit, default amiga-port). One job runs inside stefanreinauer/amiga-gcc:latest, builds mpy-cross once, then builds all four variants sequentially with make -j$(nproc) inside each. Each variant's binary is uploaded as a separate artifact named micropython-amiga-<variant>-<ref>-<sha>.

tools/amiga-build.sh mirrors the workflow exactly for local container builds — same image, same commands, same output paths — so local and CI binaries are bit-identical (see the design log's Toolchain section).

Required submodule. make -C ports/amiga submodules runs before the build; the frozen-content rule unconditionally checks for lib/micropython-lib/README.md whenever FROZEN_MANIFEST is set, even though manifest.py only freezes ports/amiga/modules.

Timing. Image pull dominates at ~87 s; the rest (checkout, submodule, mpy-cross, four variant builds, four uploads) is ~45 s — total wall time ~130 s, billed compute matches.

Possible follow-ups (deferred):

  • Image tarball cache. docker save | gzip + actions/cache would drop repeat-run wall time from ~130 s to ~40 s. Not worth the complexity for a manual-trigger workflow.
  • Release flow. When the input ref is a tag, also publish a draft GitHub release with the four binaries attached.
  • AmiSSL SDK. Phase 28 needed the SDK layered into the build environment — see that page's "SDK provisioning" subsection.

Clone this wiki locally