236 lines
9.7 KiB
Python
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()
|