Files
l2/tools/gen_linux_sl.py
2026-03-13 19:49:56 +01:00

221 lines
6.0 KiB
Python

#!/usr/bin/env python3
"""Generate stdlib/linux.sl from syscall_64.tbl metadata."""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
import re
ROOT = Path(__file__).resolve().parent.parent
SRC = ROOT / "syscall_64.tbl"
DST = ROOT / "stdlib" / "linux.sl"
def _sanitize_alias(alias: str) -> str:
name = alias.strip()
if not name:
return ""
if name.startswith("__x64_sys_"):
name = name[len("__x64_sys_") :]
elif name.startswith("sys_"):
name = name[len("sys_") :]
name = re.sub(r"[^A-Za-z0-9_]", "_", name)
name = re.sub(r"_+", "_", name).strip("_")
if not name:
return ""
if name[0].isdigit():
name = "n_" + name
return name
@dataclass(frozen=True)
class SyscallEntry:
argc: int
num: int
aliases: tuple[str, ...]
def _parse_table(path: Path) -> list[SyscallEntry]:
entries: list[SyscallEntry] = []
for raw in path.read_text(encoding="utf-8").splitlines():
line = raw.strip()
if not line or line.startswith("#"):
continue
parts = line.split(maxsplit=2)
if len(parts) < 3:
continue
try:
argc = int(parts[0])
num = int(parts[1])
except ValueError:
continue
aliases = tuple(a for a in parts[2].split("/") if a)
if not aliases:
continue
entries.append(SyscallEntry(argc=argc, num=num, aliases=aliases))
return entries
def _emit_header(lines: list[str]) -> None:
lines.extend(
[
"# Autogenerated from syscall_64.tbl",
"# Generated by tools/gen_linux_sl.py",
"# Linux syscall constants + convenience wrappers for L2",
"",
"# File descriptor constants",
"macro FD_STDIN 0 0 ;",
"macro FD_STDOUT 0 1 ;",
"macro FD_STDERR 0 2 ;",
"",
"# Common open(2) flags",
"macro O_RDONLY 0 0 ;",
"macro O_WRONLY 0 1 ;",
"macro O_RDWR 0 2 ;",
"macro O_CREAT 0 64 ;",
"macro O_EXCL 0 128 ;",
"macro O_NOCTTY 0 256 ;",
"macro O_TRUNC 0 512 ;",
"macro O_APPEND 0 1024 ;",
"macro O_NONBLOCK 0 2048 ;",
"macro O_CLOEXEC 0 524288 ;",
"",
"# lseek(2)",
"macro SEEK_SET 0 0 ;",
"macro SEEK_CUR 0 1 ;",
"macro SEEK_END 0 2 ;",
"",
"# mmap(2)",
"macro PROT_NONE 0 0 ;",
"macro PROT_READ 0 1 ;",
"macro PROT_WRITE 0 2 ;",
"macro PROT_EXEC 0 4 ;",
"macro MAP_PRIVATE 0 2 ;",
"macro MAP_ANONYMOUS 0 32 ;",
"macro MAP_SHARED 0 1 ;",
"",
"# Socket constants",
"macro AF_UNIX 0 1 ;",
"macro AF_INET 0 2 ;",
"macro AF_INET6 0 10 ;",
"macro SOCK_STREAM 0 1 ;",
"macro SOCK_DGRAM 0 2 ;",
"macro SOCK_NONBLOCK 0 2048 ;",
"macro SOCK_CLOEXEC 0 524288 ;",
"",
"macro INADDR_ANY 0 0 ;",
"",
"# Generic syscall helpers with explicit argument count",
"# Stack form:",
"# syscall -> <argN> ... <arg0> <argc> <nr> syscall",
"# syscallN -> <argN-1> ... <arg0> <nr> syscallN",
"",
"# swap impl is provided so this can be used without stdlib",
"# ___linux_swap [*, x1 | x2] -> [*, x2 | x1]",
":asm ___linux_swap {",
" mov rax, [r12]",
" mov rbx, [r12 + 8]",
" mov [r12], rbx",
" mov [r12 + 8], rax",
"}",
";",
"",
"macro syscall0 0",
" 0",
" ___linux_swap",
" syscall",
";",
"",
"macro syscall1 0",
" 1",
" ___linux_swap",
" syscall",
";",
"",
"macro syscall2 0",
" 2",
" ___linux_swap",
" syscall",
";",
"",
"macro syscall3 0",
" 3",
" ___linux_swap",
" syscall",
";",
"",
"macro syscall4 0",
" 4",
" ___linux_swap",
" syscall",
";",
"",
"macro syscall5 0",
" 5",
" ___linux_swap",
" syscall",
";",
"",
"macro syscall6 0",
" 6",
" ___linux_swap",
" syscall",
";",
"",
]
)
def _emit_entry(lines: list[str], alias: str, argc: int, num: int) -> None:
safe_argc = max(0, min(argc, 6))
lines.extend(
[
f"macro syscall.{alias} 0",
f" {num}",
f" syscall{safe_argc}",
";",
"",
f"macro syscall.{alias}.num 0",
f" {num}",
";",
"",
f"macro syscall.{alias}.argc 0",
f" {safe_argc}",
";",
"",
]
)
def generate() -> str:
entries = _parse_table(SRC)
lines: list[str] = []
_emit_header(lines)
emitted: set[str] = set()
for entry in sorted(entries, key=lambda e: (e.num, e.aliases[0])):
for alias in entry.aliases:
name = _sanitize_alias(alias)
if not name:
continue
key = f"syscall.{name}"
if key in emitted:
continue
_emit_entry(lines, name, entry.argc, entry.num)
emitted.add(key)
return "\n".join(lines).rstrip() + "\n"
def main() -> None:
output = generate()
DST.parent.mkdir(parents=True, exist_ok=True)
DST.write_text(output, encoding="utf-8")
print(f"wrote {DST}")
if __name__ == "__main__":
main()