65816-llvm-mos/scripts/genGnoKernel.py
Scott Duensing 9e53e5fd38 GNO Support
2026-05-29 15:43:28 -05:00

236 lines
9.7 KiB
Python

#!/usr/bin/env python3
# genGnoKernel.py — generate wrappers for the GNO kernel toolset.
#
# The GNO kernel is implemented as Apple IIgs user toolset $03. Each
# kernel function is identified by a 16-bit number $XX03 where XX is
# the function index and 03 is the toolset. The dispatcher is the
# standard IIgs tool dispatcher at $E10000.
#
# Function list is hard-coded from include/gno/kerntool.h in the GNO
# Consortium source (https://github.com/GnoConsortium/gno). The
# canonical ORCA-style signature is preserved in a comment.
#
# Output: runtime/src/gnoKernel.s — asm wrappers callable from our C
# ABI (arg0 in A, arg0 i32 in A:X, rest pushed RTL on stack). Each
# wrapper re-pushes args in toolbox/Pascal order (high-word first for
# 32-bit values), allocates result space, JSLs $E10000, pops the
# result back into A:X if non-void.
#
# Run after editing the function table:
# python3 scripts/genGnoKernel.py
#
# The libc layer in runtime/src/libcGno.c wraps these K* primitives
# into POSIX names (fork, exec, wait, etc.).
from pathlib import Path
OUT_ASM = Path("/home/scott/claude/llvm816/runtime/src/gnoKernel.s")
OUT_HEADER = Path("/home/scott/claude/llvm816/runtime/include/gno/kernel.h")
# Each entry: (funcId, name, retSize, [argSize, ...])
# Sizes in bytes (2 = i16, 4 = i32 / ptr32). Names follow ORCA's K* prefix.
#
# Pulled from include/gno/kerntool.h in the GNO Consortium repo.
SYSCALLS = [
# ---- Process control ----
(0x0903, "Kgetpid", 2, []), # () -> int
(0x0A03, "Kkill", 2, [2, 2, 4]), # (pid, sig, *errno) -> int
(0x0B03, "Kfork", 2, [4, 4]), # (*subr, *errno) -> int
(0x0903, "Kgetpid_dup", 2, []), # alias kept for sanity check
(0x4003, "Kgetppid", 2, [4]), # (*errno) -> int
(0x1703, "Kwait", 2, [4, 4]), # (*status, *errno) -> int
(0x1D03, "K_execve", 2, [4, 4, 4]), # (*file, *cmdline, *err) -> int
(0x1603, "Ksignal", 4, [2, 4, 4]), # (sig, func_ptr, *err) -> sig_t
(0x1E03, "Kalarm", 4, [4, 4]), # (seconds, *errno) -> longword
(0x4203, "Kalarm10", 4, [4, 4]), # (seconds-tenths, *errno) -> longword
(0x2103, "Ksigpause", 2, [4, 4]), # (mask, *errno) -> int
(0x1B03, "Ksigsetmask", 4, [4, 4]), # (mask, *errno) -> longword
(0x1C03, "Ksigblock", 4, [4, 4]), # (mask, *errno) -> longword
# ---- File descriptors ----
(0x2203, "Kdup", 2, [2, 4]), # (oldfd, *errno) -> int
(0x2303, "Kdup2", 2, [2, 2, 4]), # (oldfd, newfd, *errno) -> int
(0x2403, "Kpipe", 2, [4, 4]), # (*fildes[2], *errno) -> int
(0x2603, "Kioctl", 2, [2, 4, 4, 4]), # (fd, req:ulong, *ptr, *errno) -> int
# ---- stat ----
(0x2703, "Kstat", 2, [4, 4, 4]), # (*path, *sbuf, *errno) -> int
(0x2803, "Kfstat", 2, [2, 4, 4]), # (fd, *sbuf, *errno) -> int
(0x2903, "Klstat", 2, [4, 4, 4]), # (*path, *sbuf, *errno) -> int
# ---- IDs ----
(0x2A03, "Kgetuid", 2, [4]), # (*errno) -> int
(0x2B03, "Kgetgid", 2, [4]), # (*errno) -> int
(0x2C03, "Kgeteuid", 2, [4]), # (*errno) -> int
(0x2D03, "Kgetegid", 2, [4]), # (*errno) -> int
(0x2E03, "Ksetuid", 2, [2, 4]), # (uid, *errno) -> int
(0x2F03, "Ksetgid", 2, [2, 4]), # (gid, *errno) -> int
# ---- Process groups / TTY ----
(0x1803, "Ktcnewpgrp", 2, [2, 4]), # (fdtty, *errno) -> int
(0x1903, "Ksettpgrp", 2, [2, 4]), # (fdtty, *errno) -> int
(0x1A03, "Ktctpgrp", 2, [2, 2, 4]), # (fdtty, pid, *err) -> int
(0x2503, "K_getpgrp", 2, [2, 4]), # (pid, *errno) -> pid_t
(0x3403, "Ksetpgrp", 2, [2, 2, 4]), # (pid, pgrp, *errno) -> int
# ---- Kernel VM (process inspection) ----
(0x1103, "Kkvm_open", 2, [4]), # (*errno) -> int
(0x1203, "Kkvm_close", 2, [4, 4]), # (kvmt, *errno) -> int
# ---- Misc ----
(0x3503, "Ktimes", 4, [4, 4]), # (*tms, *errno) -> clock_t
# ---- Quit setup ----
(0x4103, "KSetGNOQuitRec", 0, [2, 4, 2, 4]), # (pCount, GSStringPtr, flags, *err) -> void
]
DISPATCHER = 0xE10000
def emitWrapper(funcId, name, retSize, argSizes):
"""Emit one .s function body for the given GNO kernel call."""
lines = []
argTypes = [f"i{size*8}" for size in argSizes]
lines.append(f"; {name}({', '.join(argTypes) or 'void'}) -> "
f"{'void' if retSize == 0 else f'i{retSize*8}'}")
lines.append(f"; toolset 3, func 0x{funcId:04X}")
lines.append(f"\t.section .text.{name},\"ax\"")
lines.append(f"\t.globl {name}")
lines.append(f"{name}:")
scratchDP = 0xE0
firstArgIs32 = bool(argSizes) and argSizes[0] == 4
# Stash arg0 if any args. arg0 in A (i16) or A:X (i32).
if argSizes:
lines.append(f"\t; --- stash arg0 (in A{'/X' if firstArgIs32 else ''}) ---")
lines.append(f"\tsta 0x{scratchDP:02X}")
if firstArgIs32:
lines.append(f"\tstx 0x{scratchDP + 2:02X}")
# Result space (rounded up to whole words).
resultWords = (retSize + 1) // 2
if resultWords > 0:
lines.append(f"\t; --- result space ({retSize} bytes) ---")
for _ in range(resultWords):
lines.append("\tpea 0")
# Push args in Pascal order (high-word first for 32-bit).
pushedBytes = resultWords * 2
# arg0.
if argSizes:
lines.append("\t; --- arg0 ---")
if firstArgIs32:
lines.append(f"\tlda 0x{scratchDP + 2:02X}")
lines.append("\tpha")
pushedBytes += 2
lines.append(f"\tlda 0x{scratchDP:02X}")
lines.append("\tpha")
pushedBytes += 2
# arg1+.
stackArgOffset = 4 # caller's first stack arg (after 3-byte JSL ret addr)
for i, size in enumerate(argSizes[1:], start=1):
lines.append(f"\t; --- arg{i} ({size}B) ---")
if size <= 2:
lines.append(f"\tlda {stackArgOffset + pushedBytes}, s")
lines.append("\tpha")
pushedBytes += 2
stackArgOffset += 2
elif size == 4:
# High word first. Caller pushed low-word-first, so HI is at
# caller offset +2. After PHA, the LO becomes accessible at
# the SAME stack-rel offset (PHA shifted SP by 2).
lines.append(f"\tlda {stackArgOffset + pushedBytes + 2}, s")
lines.append("\tpha")
pushedBytes += 2
lines.append(f"\tlda {stackArgOffset + pushedBytes}, s")
lines.append("\tpha")
pushedBytes += 2
stackArgOffset += 4
else:
raise RuntimeError(f"unhandled arg size {size} in {name}")
# Dispatch.
lines.append(f"\tldx #0x{funcId:04X}")
lines.append(f"\tjsl 0x{DISPATCHER:x}")
# Pop result.
if resultWords > 0:
lines.append("\tpla ; result lo -> A")
if resultWords == 2:
lines.append("\tplx ; result hi -> X")
lines.append("\trtl")
lines.append("")
return lines
def emitHeader():
"""Generate a C header with extern decls for use by libc."""
hLines = [
"// AUTOGENERATED by scripts/genGnoKernel.py — DO NOT EDIT.",
"// GNO kernel toolset $03 wrappers, callable from C.",
"// Convention: each K* returns the kernel result (or -1 on error);",
"// the last argument is `int *errno` and gets the kernel's errno.",
"//",
"// These are LOW-LEVEL primitives — libc routines in libcGno.c",
"// wrap them into POSIX-named fork/exec/wait/etc.",
"#ifndef GNO_KERNEL_H",
"#define GNO_KERNEL_H",
"",
"#include <stdint.h>",
"",
]
for funcId, name, retSize, argSizes in SYSCALLS:
# Skip the sanity-check duplicate.
if name.endswith("_dup"):
continue
retC = "void" if retSize == 0 else (
"int" if retSize == 2 else "unsigned long")
argC = []
for i, size in enumerate(argSizes):
t = "int" if size == 2 else "void *"
argC.append(f"{t} a{i}")
hLines.append(f"extern {retC} {name}({', '.join(argC) or 'void'});"
f" // 0x{funcId:04X}")
hLines.append("")
hLines.append("#endif")
hLines.append("")
return hLines
def main():
asmLines = [
"; AUTOGENERATED by scripts/genGnoKernel.py — DO NOT EDIT by hand.",
";",
"; GNO/ME kernel toolset $03 wrappers.",
"; Dispatcher: JSL $E10000 with LDX #funcId.",
";",
"; C ABI: arg0 (i16) in A, arg0 (i32) in A:X, arg1+ on stack",
"; (caller-pushed, lo-word first for 32-bit values).",
"; Each wrapper re-pushes args in Pascal order (high-word first",
"; for 32-bit), allocates result space, dispatches, pops result.",
"",
"\t.text",
"",
]
seen = set()
for funcId, name, retSize, argSizes in SYSCALLS:
if name in seen:
continue
seen.add(name)
asmLines.extend(emitWrapper(funcId, name, retSize, argSizes))
OUT_ASM.parent.mkdir(parents=True, exist_ok=True)
OUT_ASM.write_text("\n".join(asmLines))
print(f"wrote {OUT_ASM} ({len(asmLines)} lines, "
f"{len(seen) - 1} wrappers)")
OUT_HEADER.parent.mkdir(parents=True, exist_ok=True)
OUT_HEADER.write_text("\n".join(emitHeader()))
print(f"wrote {OUT_HEADER}")
if __name__ == "__main__":
main()