/// Enumeration of all System Calls (Syscalls) available in the Prometeu environment. /// /// Syscalls are the primary mechanism for a program running in the Virtual Machine /// to interact with the outside world (Hardware, OS, Filesystem). /// /// Each Syscall has a unique 32-bit ID. The IDs are grouped by category: /// - **0x0xxx**: System & OS Control /// - **0x1xxx**: Graphics (GFX) /// - **0x2xxx**: Input (Gamepad & Touch) /// - **0x3xxx**: Audio (PCM & Mixing) /// - **0x4xxx**: Filesystem (Sandboxed I/O) /// - **0x5xxx**: Logging & Debugging /// - **0x6xxx**: Asset Loading & Memory Banks #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u32)] pub enum Syscall { // --- System --- /// Checks if a cartridge is currently inserted in the virtual slot. SystemHasCart = 0x0001, /// Requests the OS to launch a specific cartridge. SystemRunCart = 0x0002, // --- GFX (Graphics) --- /// Fills the entire back buffer with a single color. GfxClear = 0x1001, /// Draws a solid rectangle. GfxFillRect = 0x1002, /// Draws a 1-pixel wide line. GfxDrawLine = 0x1003, /// Draws a circle outline. GfxDrawCircle = 0x1004, /// Draws a filled circle (disc). GfxDrawDisc = 0x1005, /// Draws a rectangle outline. GfxDrawSquare = 0x1006, /// Configures one of the 512 hardware sprites. GfxSetSprite = 0x1007, /// Draws a text string at the specified coordinates. GfxDrawText = 0x1008, /// Fills the entire back buffer with a single RGB565 color (flattened). GfxClear565 = 0x1010, // --- Input --- /// Returns the current raw state of the digital gamepad (bitmask). InputGetPad = 0x2001, /// Returns buttons that were pressed exactly in this frame. InputGetPadPressed = 0x2002, /// Returns buttons that were released exactly in this frame. InputGetPadReleased = 0x2003, /// Returns how many frames a button has been held down. InputGetPadHold = 0x2004, /// Returns the full snapshot of the gamepad state (48 slots). InputPadSnapshot = 0x2010, /// Returns the full snapshot of the touch state (6 slots). InputTouchSnapshot = 0x2011, /// Returns the X coordinate of the touch/mouse pointer. TouchGetX = 0x2101, /// Returns the Y coordinate of the touch/mouse pointer. TouchGetY = 0x2102, /// Returns true if the pointer is currently touching the screen. TouchIsDown = 0x2103, /// Returns true if the touch started in this frame. TouchIsPressed = 0x2104, /// Returns true if the touch ended in this frame. TouchIsReleased = 0x2105, /// Returns how many frames the pointer has been held down. TouchGetHold = 0x2106, /// Returns the full Button struct (6 bytes) for the touch finger state. TouchGetFinger = 0x2107, // --- Input (Pad service-based) --- /// Returns the full Button struct (6 bytes) for each gamepad button PadGetUp = 0x2200, PadGetDown = 0x2201, PadGetLeft = 0x2202, PadGetRight = 0x2203, PadGetA = 0x2204, PadGetB = 0x2205, PadGetX = 0x2206, PadGetY = 0x2207, PadGetL = 0x2208, PadGetR = 0x2209, PadGetStart = 0x220A, PadGetSelect = 0x220B, // --- Audio --- /// Starts playback of a sound sample by its Bank and ID. AudioPlaySample = 0x3001, /// Low-level audio play command. AudioPlay = 0x3002, // --- FS (Filesystem) --- /// Opens a file for reading or writing. Returns a File Handle (u32). FsOpen = 0x4001, /// Reads data from an open file handle into the VM heap. FsRead = 0x4002, /// Writes data from the VM heap into an open file handle. FsWrite = 0x4003, /// Closes an open file handle. FsClose = 0x4004, /// Lists entries in a directory. FsListDir = 0x4005, /// Checks if a file or directory exists. FsExists = 0x4006, /// Deletes a file or empty directory. FsDelete = 0x4007, // --- Log --- /// Writes a generic string to the system log. LogWrite = 0x5001, /// Writes a string to the system log with a specific numerical tag. LogWriteTag = 0x5002, // --- Asset (DMA) --- /// Starts an asynchronous load of a file into a memory bank. AssetLoad = 0x6001, /// Returns the status of a pending asset load (0=Loading, 1=Ready, 2=Error). AssetStatus = 0x6002, /// Finalizes the asset loading, making it available for GFX/Audio. AssetCommit = 0x6003, /// Cancels a pending asset load. AssetCancel = 0x6004, // --- Bank (Memory) --- /// Returns information about a specific Memory Bank. BankInfo = 0x6101, /// Returns information about a slot within a Memory Bank. BankSlotInfo = 0x6102, } /// Canonical metadata describing a syscall using the unified slot-based ABI. /// /// This structure is the single source of truth for: /// - Argument slot count (inputs pulled from the VM stack) /// - Return slot count (values pushed back to the VM stack) /// - Capability flags (what permission is required to call this syscall) /// - Determinism characteristics (if known/defined by the spec) #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct SyscallMeta { /// Numeric identifier of the syscall (matches the enum discriminant). pub id: u32, /// Canonical module name for this syscall (e.g., "gfx", "input"). pub module: &'static str, /// Canonical syscall name (snake_case, without module prefix). pub name: &'static str, /// Canonical ABI version for this syscall identity. pub version: u16, /// Number of input slots consumed from the VM stack. pub arg_slots: u8, /// Number of output slots produced onto the VM stack. pub ret_slots: u16, /// Capability flags required for this syscall. pub caps: CapFlags, /// Determinism characteristics for the syscall. pub determinism: Determinism, /// Whether this syscall may allocate VM heap objects. pub may_allocate: bool, /// A coarse execution cost hint used by verifiers/schedulers. pub cost_hint: u32, } /// Bitflags representing capabilities required to invoke a syscall. /// /// This avoids adding a new dependency; flags are represented in a plain /// `u64` and combined via bitwise OR. Extend as needed as the capability /// model evolves. pub type CapFlags = u64; pub mod caps { use super::CapFlags; pub const NONE: CapFlags = 0; pub const SYSTEM: CapFlags = 1 << 0; pub const GFX: CapFlags = 1 << 1; pub const INPUT: CapFlags = 1 << 2; pub const AUDIO: CapFlags = 1 << 3; pub const FS: CapFlags = 1 << 4; pub const LOG: CapFlags = 1 << 5; pub const ASSET: CapFlags = 1 << 6; pub const BANK: CapFlags = 1 << 7; } /// Determinism flags for a syscall. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Determinism { /// Determinism is not specified in the current spec. Unknown, /// Given the same VM state and inputs, result is deterministic. Deterministic, /// May vary across runs (e.g., time, external IO race), even with same inputs. NonDeterministic, } /// Pairing of a strongly-typed syscall and its metadata. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct SyscallRegistryEntry { pub syscall: Syscall, pub meta: SyscallMeta, } /// Canonical identity triple for a syscall. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct SyscallIdentity { pub module: &'static str, pub name: &'static str, pub version: u16, } impl SyscallIdentity { pub fn key(&self) -> (&'static str, &'static str, u16) { (self.module, self.name, self.version) } } /// Resolved syscall information provided to the loader/VM. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct SyscallResolved { /// Numeric syscall id used at runtime (VM executes `SYSCALL ` only). pub id: u32, /// Associated metadata for verification/runtime checks. pub meta: SyscallMeta, } /// Load-time error for syscall resolution. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum LoadError { /// The (module, name, version) triple is not known by the host. UnknownSyscall { module: &'static str, name: &'static str, version: u16, }, /// The cartridge lacks required capabilities for the syscall. MissingCapability { required: CapFlags, provided: CapFlags, module: &'static str, name: &'static str, version: u16, }, } /// Resolve a canonical syscall identity to its numeric id and metadata. /// /// Returns `None` if the identity is unknown. pub fn resolve_syscall(module: &'static str, name: &'static str, version: u16) -> Option { for entry in SYSCALL_TABLE { if entry.meta.module == module && entry.meta.name == name && entry.meta.version == version { return Some(SyscallResolved { id: entry.meta.id, meta: entry.meta }); } } None } /// Resolve all declared program syscalls and enforce capability gating. /// /// - `declared`: list of canonical identities required by the program. /// - `caps`: capabilities granted to the cartridge/program. /// /// Fails deterministically if any syscall is unknown or requires missing caps. pub fn resolve_program_syscalls( declared: &[SyscallIdentity], caps: CapFlags, ) -> Result, LoadError> { let mut out = Vec::with_capacity(declared.len()); for ident in declared { let Some(res) = resolve_syscall(ident.module, ident.name, ident.version) else { return Err(LoadError::UnknownSyscall { module: ident.module, name: ident.name, version: ident.version }); }; // Capability gating: required must be subset of provided let missing = res.meta.caps & !caps; if missing != 0 { return Err(LoadError::MissingCapability { required: res.meta.caps, provided: caps, module: ident.module, name: ident.name, version: ident.version, }); } out.push(res); } Ok(out) } /// Canonical registry of all syscalls and their metadata. /// /// IMPORTANT: This table is the single authoritative source for the slot-based /// ABI. All helper methods (e.g., `args_count`/`results_count`) must read from /// this table. pub const SYSCALL_TABLE: &[SyscallRegistryEntry] = &[ // --- System --- SyscallRegistryEntry { syscall: Syscall::SystemHasCart, meta: SyscallMeta { id: 0x0001, module: "system", name: "has_cart", version: 1, arg_slots: 0, ret_slots: 1, caps: caps::SYSTEM, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } }, SyscallRegistryEntry { syscall: Syscall::SystemRunCart, meta: SyscallMeta { id: 0x0002, module: "system", name: "run_cart", version: 1, arg_slots: 0, ret_slots: 0, caps: caps::SYSTEM, determinism: Determinism::NonDeterministic, may_allocate: false, cost_hint: 50 } }, // --- GFX --- SyscallRegistryEntry { syscall: Syscall::GfxClear, meta: SyscallMeta { id: 0x1001, module: "gfx", name: "clear", version: 1, arg_slots: 1, ret_slots: 0, caps: caps::GFX, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 20 } }, SyscallRegistryEntry { syscall: Syscall::GfxFillRect, meta: SyscallMeta { id: 0x1002, module: "gfx", name: "fill_rect", version: 1, arg_slots: 5, ret_slots: 0, caps: caps::GFX, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 20 } }, SyscallRegistryEntry { syscall: Syscall::GfxDrawLine, meta: SyscallMeta { id: 0x1003, module: "gfx", name: "draw_line", version: 1, arg_slots: 5, ret_slots: 0, caps: caps::GFX, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 5 } }, SyscallRegistryEntry { syscall: Syscall::GfxDrawCircle, meta: SyscallMeta { id: 0x1004, module: "gfx", name: "draw_circle", version: 1, arg_slots: 4, ret_slots: 0, caps: caps::GFX, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 5 } }, SyscallRegistryEntry { syscall: Syscall::GfxDrawDisc, meta: SyscallMeta { id: 0x1005, module: "gfx", name: "draw_disc", version: 1, arg_slots: 5, ret_slots: 0, caps: caps::GFX, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 5 } }, SyscallRegistryEntry { syscall: Syscall::GfxDrawSquare, meta: SyscallMeta { id: 0x1006, module: "gfx", name: "draw_square", version: 1, arg_slots: 6, ret_slots: 0, caps: caps::GFX, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 5 } }, SyscallRegistryEntry { syscall: Syscall::GfxSetSprite, meta: SyscallMeta { id: 0x1007, module: "gfx", name: "set_sprite", version: 1, arg_slots: 10, ret_slots: 0, caps: caps::GFX, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 5 } }, SyscallRegistryEntry { syscall: Syscall::GfxDrawText, meta: SyscallMeta { id: 0x1008, module: "gfx", name: "draw_text", version: 1, arg_slots: 4, ret_slots: 0, caps: caps::GFX, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 20 } }, SyscallRegistryEntry { syscall: Syscall::GfxClear565, meta: SyscallMeta { id: 0x1010, module: "gfx", name: "clear_565", version: 1, arg_slots: 1, ret_slots: 0, caps: caps::GFX, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 20 } }, // --- Input --- SyscallRegistryEntry { syscall: Syscall::InputGetPad, meta: SyscallMeta { id: 0x2001, module: "input", name: "get_pad", version: 1, arg_slots: 1, ret_slots: 1, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } }, SyscallRegistryEntry { syscall: Syscall::InputGetPadPressed, meta: SyscallMeta { id: 0x2002, module: "input", name: "get_pad_pressed", version: 1, arg_slots: 1, ret_slots: 1, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } }, SyscallRegistryEntry { syscall: Syscall::InputGetPadReleased, meta: SyscallMeta { id: 0x2003, module: "input", name: "get_pad_released", version: 1, arg_slots: 1, ret_slots: 1, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } }, SyscallRegistryEntry { syscall: Syscall::InputGetPadHold, meta: SyscallMeta { id: 0x2004, module: "input", name: "get_pad_hold", version: 1, arg_slots: 1, ret_slots: 1, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } }, SyscallRegistryEntry { syscall: Syscall::InputPadSnapshot, meta: SyscallMeta { id: 0x2010, module: "input", name: "pad_snapshot", version: 1, arg_slots: 0, ret_slots: 48, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } }, SyscallRegistryEntry { syscall: Syscall::InputTouchSnapshot, meta: SyscallMeta { id: 0x2011, module: "input", name: "touch_snapshot", version: 1, arg_slots: 0, ret_slots: 6, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } }, SyscallRegistryEntry { syscall: Syscall::TouchGetX, meta: SyscallMeta { id: 0x2101, module: "input", name: "touch_get_x", version: 1, arg_slots: 0, ret_slots: 1, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } }, SyscallRegistryEntry { syscall: Syscall::TouchGetY, meta: SyscallMeta { id: 0x2102, module: "input", name: "touch_get_y", version: 1, arg_slots: 0, ret_slots: 1, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } }, SyscallRegistryEntry { syscall: Syscall::TouchIsDown, meta: SyscallMeta { id: 0x2103, module: "input", name: "touch_is_down", version: 1, arg_slots: 0, ret_slots: 1, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } }, SyscallRegistryEntry { syscall: Syscall::TouchIsPressed, meta: SyscallMeta { id: 0x2104, module: "input", name: "touch_is_pressed", version: 1, arg_slots: 0, ret_slots: 1, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } }, SyscallRegistryEntry { syscall: Syscall::TouchIsReleased, meta: SyscallMeta { id: 0x2105, module: "input", name: "touch_is_released", version: 1, arg_slots: 0, ret_slots: 1, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } }, SyscallRegistryEntry { syscall: Syscall::TouchGetHold, meta: SyscallMeta { id: 0x2106, module: "input", name: "touch_get_hold", version: 1, arg_slots: 0, ret_slots: 1, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } }, SyscallRegistryEntry { syscall: Syscall::TouchGetFinger, meta: SyscallMeta { id: 0x2107, module: "input", name: "touch_get_finger", version: 1, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } }, // --- Input (Pad service-based) --- SyscallRegistryEntry { syscall: Syscall::PadGetUp, meta: SyscallMeta { id: 0x2200, module: "input", name: "pad_get_up", version: 1, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } }, SyscallRegistryEntry { syscall: Syscall::PadGetDown, meta: SyscallMeta { id: 0x2201, module: "input", name: "pad_get_down", version: 1, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } }, SyscallRegistryEntry { syscall: Syscall::PadGetLeft, meta: SyscallMeta { id: 0x2202, module: "input", name: "pad_get_left", version: 1, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } }, SyscallRegistryEntry { syscall: Syscall::PadGetRight, meta: SyscallMeta { id: 0x2203, module: "input", name: "pad_get_right", version: 1, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } }, SyscallRegistryEntry { syscall: Syscall::PadGetA, meta: SyscallMeta { id: 0x2204, module: "input", name: "pad_get_a", version: 1, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } }, SyscallRegistryEntry { syscall: Syscall::PadGetB, meta: SyscallMeta { id: 0x2205, module: "input", name: "pad_get_b", version: 1, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } }, SyscallRegistryEntry { syscall: Syscall::PadGetX, meta: SyscallMeta { id: 0x2206, module: "input", name: "pad_get_x", version: 1, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } }, SyscallRegistryEntry { syscall: Syscall::PadGetY, meta: SyscallMeta { id: 0x2207, module: "input", name: "pad_get_y", version: 1, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } }, SyscallRegistryEntry { syscall: Syscall::PadGetL, meta: SyscallMeta { id: 0x2208, module: "input", name: "pad_get_l", version: 1, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } }, SyscallRegistryEntry { syscall: Syscall::PadGetR, meta: SyscallMeta { id: 0x2209, module: "input", name: "pad_get_r", version: 1, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } }, SyscallRegistryEntry { syscall: Syscall::PadGetStart, meta: SyscallMeta { id: 0x220A, module: "input", name: "pad_get_start", version: 1, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } }, SyscallRegistryEntry { syscall: Syscall::PadGetSelect, meta: SyscallMeta { id: 0x220B, module: "input", name: "pad_get_select", version: 1, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } }, // --- Audio --- SyscallRegistryEntry { syscall: Syscall::AudioPlaySample, meta: SyscallMeta { id: 0x3001, module: "audio", name: "play_sample", version: 1, arg_slots: 5, ret_slots: 0, caps: caps::AUDIO, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 5 } }, SyscallRegistryEntry { syscall: Syscall::AudioPlay, meta: SyscallMeta { id: 0x3002, module: "audio", name: "play", version: 1, arg_slots: 7, ret_slots: 0, caps: caps::AUDIO, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 5 } }, // --- FS --- SyscallRegistryEntry { syscall: Syscall::FsOpen, meta: SyscallMeta { id: 0x4001, module: "fs", name: "open", version: 1, arg_slots: 1, ret_slots: 1, caps: caps::FS, determinism: Determinism::NonDeterministic, may_allocate: false, cost_hint: 20 } }, SyscallRegistryEntry { syscall: Syscall::FsRead, meta: SyscallMeta { id: 0x4002, module: "fs", name: "read", version: 1, arg_slots: 1, ret_slots: 1, caps: caps::FS, determinism: Determinism::NonDeterministic, may_allocate: false, cost_hint: 20 } }, SyscallRegistryEntry { syscall: Syscall::FsWrite, meta: SyscallMeta { id: 0x4003, module: "fs", name: "write", version: 1, arg_slots: 2, ret_slots: 1, caps: caps::FS, determinism: Determinism::NonDeterministic, may_allocate: false, cost_hint: 20 } }, SyscallRegistryEntry { syscall: Syscall::FsClose, meta: SyscallMeta { id: 0x4004, module: "fs", name: "close", version: 1, arg_slots: 1, ret_slots: 0, caps: caps::FS, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 5 } }, SyscallRegistryEntry { syscall: Syscall::FsListDir, meta: SyscallMeta { id: 0x4005, module: "fs", name: "list_dir", version: 1, arg_slots: 1, ret_slots: 1, caps: caps::FS, determinism: Determinism::NonDeterministic, may_allocate: false, cost_hint: 20 } }, SyscallRegistryEntry { syscall: Syscall::FsExists, meta: SyscallMeta { id: 0x4006, module: "fs", name: "exists", version: 1, arg_slots: 1, ret_slots: 1, caps: caps::FS, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } }, SyscallRegistryEntry { syscall: Syscall::FsDelete, meta: SyscallMeta { id: 0x4007, module: "fs", name: "delete", version: 1, arg_slots: 1, ret_slots: 0, caps: caps::FS, determinism: Determinism::NonDeterministic, may_allocate: false, cost_hint: 20 } }, // --- Log --- SyscallRegistryEntry { syscall: Syscall::LogWrite, meta: SyscallMeta { id: 0x5001, module: "log", name: "write", version: 1, arg_slots: 2, ret_slots: 0, caps: caps::LOG, determinism: Determinism::NonDeterministic, may_allocate: false, cost_hint: 5 } }, SyscallRegistryEntry { syscall: Syscall::LogWriteTag, meta: SyscallMeta { id: 0x5002, module: "log", name: "write_tag", version: 1, arg_slots: 3, ret_slots: 0, caps: caps::LOG, determinism: Determinism::NonDeterministic, may_allocate: false, cost_hint: 5 } }, // --- Asset/Bank --- SyscallRegistryEntry { syscall: Syscall::AssetLoad, meta: SyscallMeta { id: 0x6001, module: "asset", name: "load", version: 1, arg_slots: 3, ret_slots: 1, caps: caps::ASSET, determinism: Determinism::NonDeterministic, may_allocate: false, cost_hint: 20 } }, SyscallRegistryEntry { syscall: Syscall::AssetStatus, meta: SyscallMeta { id: 0x6002, module: "asset", name: "status", version: 1, arg_slots: 1, ret_slots: 1, caps: caps::ASSET, determinism: Determinism::NonDeterministic, may_allocate: false, cost_hint: 1 } }, SyscallRegistryEntry { syscall: Syscall::AssetCommit, meta: SyscallMeta { id: 0x6003, module: "asset", name: "commit", version: 1, arg_slots: 1, ret_slots: 0, caps: caps::ASSET, determinism: Determinism::NonDeterministic, may_allocate: false, cost_hint: 20 } }, SyscallRegistryEntry { syscall: Syscall::AssetCancel, meta: SyscallMeta { id: 0x6004, module: "asset", name: "cancel", version: 1, arg_slots: 1, ret_slots: 0, caps: caps::ASSET, determinism: Determinism::NonDeterministic, may_allocate: false, cost_hint: 20 } }, SyscallRegistryEntry { syscall: Syscall::BankInfo, meta: SyscallMeta { id: 0x6101, module: "bank", name: "info", version: 1, arg_slots: 1, ret_slots: 1, caps: caps::BANK, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } }, SyscallRegistryEntry { syscall: Syscall::BankSlotInfo, meta: SyscallMeta { id: 0x6102, module: "bank", name: "slot_info", version: 1, arg_slots: 2, ret_slots: 1, caps: caps::BANK, determinism: Determinism::Deterministic, may_allocate: false, cost_hint: 1 } }, ]; /// Returns the metadata associated with this syscall. pub fn meta_for(syscall: Syscall) -> &'static SyscallMeta { // Linear scan is acceptable given the very small number of syscalls. // If this grows substantially, replace with a perfect hash or match. for entry in SYSCALL_TABLE { if entry.syscall == syscall { return &entry.meta; } } panic!("Missing SyscallMeta for {:?}", syscall); } impl Syscall { pub fn from_u32(id: u32) -> Option { match id { 0x0001 => Some(Self::SystemHasCart), 0x0002 => Some(Self::SystemRunCart), 0x1001 => Some(Self::GfxClear), 0x1002 => Some(Self::GfxFillRect), 0x1003 => Some(Self::GfxDrawLine), 0x1004 => Some(Self::GfxDrawCircle), 0x1005 => Some(Self::GfxDrawDisc), 0x1006 => Some(Self::GfxDrawSquare), 0x1007 => Some(Self::GfxSetSprite), 0x1008 => Some(Self::GfxDrawText), 0x1010 => Some(Self::GfxClear565), 0x2001 => Some(Self::InputGetPad), 0x2002 => Some(Self::InputGetPadPressed), 0x2003 => Some(Self::InputGetPadReleased), 0x2004 => Some(Self::InputGetPadHold), 0x2010 => Some(Self::InputPadSnapshot), 0x2011 => Some(Self::InputTouchSnapshot), 0x2101 => Some(Self::TouchGetX), 0x2102 => Some(Self::TouchGetY), 0x2103 => Some(Self::TouchIsDown), 0x2104 => Some(Self::TouchIsPressed), 0x2105 => Some(Self::TouchIsReleased), 0x2106 => Some(Self::TouchGetHold), 0x2107 => Some(Self::TouchGetFinger), 0x2200 => Some(Self::PadGetUp), 0x2201 => Some(Self::PadGetDown), 0x2202 => Some(Self::PadGetLeft), 0x2203 => Some(Self::PadGetRight), 0x2204 => Some(Self::PadGetA), 0x2205 => Some(Self::PadGetB), 0x2206 => Some(Self::PadGetX), 0x2207 => Some(Self::PadGetY), 0x2208 => Some(Self::PadGetL), 0x2209 => Some(Self::PadGetR), 0x220A => Some(Self::PadGetStart), 0x220B => Some(Self::PadGetSelect), 0x3001 => Some(Self::AudioPlaySample), 0x3002 => Some(Self::AudioPlay), 0x4001 => Some(Self::FsOpen), 0x4002 => Some(Self::FsRead), 0x4003 => Some(Self::FsWrite), 0x4004 => Some(Self::FsClose), 0x4005 => Some(Self::FsListDir), 0x4006 => Some(Self::FsExists), 0x4007 => Some(Self::FsDelete), 0x5001 => Some(Self::LogWrite), 0x5002 => Some(Self::LogWriteTag), 0x6001 => Some(Self::AssetLoad), 0x6002 => Some(Self::AssetStatus), 0x6003 => Some(Self::AssetCommit), 0x6004 => Some(Self::AssetCancel), 0x6101 => Some(Self::BankInfo), 0x6102 => Some(Self::BankSlotInfo), _ => None, } } pub fn args_count(&self) -> usize { meta_for(*self).arg_slots as usize } pub fn results_count(&self) -> usize { meta_for(*self).ret_slots as usize } pub fn name(&self) -> &'static str { match self { Self::SystemHasCart => "SystemHasCart", Self::SystemRunCart => "SystemRunCart", Self::GfxClear => "GfxClear", Self::GfxFillRect => "GfxFillRect", Self::GfxDrawLine => "GfxDrawLine", Self::GfxDrawCircle => "GfxDrawCircle", Self::GfxDrawDisc => "GfxDrawDisc", Self::GfxDrawSquare => "GfxDrawSquare", Self::GfxSetSprite => "GfxSetSprite", Self::GfxDrawText => "GfxDrawText", Self::GfxClear565 => "GfxClear565", Self::InputGetPad => "InputGetPad", Self::InputGetPadPressed => "InputGetPadPressed", Self::InputGetPadReleased => "InputGetPadReleased", Self::InputGetPadHold => "InputGetPadHold", Self::InputPadSnapshot => "InputPadSnapshot", Self::InputTouchSnapshot => "InputTouchSnapshot", Self::TouchGetX => "TouchGetX", Self::TouchGetY => "TouchGetY", Self::TouchIsDown => "TouchIsDown", Self::TouchIsPressed => "TouchIsPressed", Self::TouchIsReleased => "TouchIsReleased", Self::TouchGetHold => "TouchGetHold", Self::TouchGetFinger => "TouchGetFinger", Self::PadGetUp => "PadGetUp", Self::PadGetDown => "PadGetDown", Self::PadGetLeft => "PadGetLeft", Self::PadGetRight => "PadGetRight", Self::PadGetA => "PadGetA", Self::PadGetB => "PadGetB", Self::PadGetX => "PadGetX", Self::PadGetY => "PadGetY", Self::PadGetL => "PadGetL", Self::PadGetR => "PadGetR", Self::PadGetStart => "PadGetStart", Self::PadGetSelect => "PadGetSelect", Self::AudioPlaySample => "AudioPlaySample", Self::AudioPlay => "AudioPlay", Self::FsOpen => "FsOpen", Self::FsRead => "FsRead", Self::FsWrite => "FsWrite", Self::FsClose => "FsClose", Self::FsListDir => "FsListDir", Self::FsExists => "FsExists", Self::FsDelete => "FsDelete", Self::LogWrite => "LogWrite", Self::LogWriteTag => "LogWriteTag", Self::AssetLoad => "AssetLoad", Self::AssetStatus => "AssetStatus", Self::AssetCommit => "AssetCommit", Self::AssetCancel => "AssetCancel", Self::BankInfo => "BankInfo", Self::BankSlotInfo => "BankSlotInfo", } } } #[cfg(test)] mod tests { use super::*; fn all_syscalls() -> &'static [Syscall] { &[ // System Syscall::SystemHasCart, Syscall::SystemRunCart, // GFX Syscall::GfxClear, Syscall::GfxFillRect, Syscall::GfxDrawLine, Syscall::GfxDrawCircle, Syscall::GfxDrawDisc, Syscall::GfxDrawSquare, Syscall::GfxSetSprite, Syscall::GfxDrawText, Syscall::GfxClear565, // Input Syscall::InputGetPad, Syscall::InputGetPadPressed, Syscall::InputGetPadReleased, Syscall::InputGetPadHold, Syscall::InputPadSnapshot, Syscall::InputTouchSnapshot, Syscall::TouchGetX, Syscall::TouchGetY, Syscall::TouchIsDown, Syscall::TouchIsPressed, Syscall::TouchIsReleased, Syscall::TouchGetHold, Syscall::TouchGetFinger, // Pad service Syscall::PadGetUp, Syscall::PadGetDown, Syscall::PadGetLeft, Syscall::PadGetRight, Syscall::PadGetA, Syscall::PadGetB, Syscall::PadGetX, Syscall::PadGetY, Syscall::PadGetL, Syscall::PadGetR, Syscall::PadGetStart, Syscall::PadGetSelect, // Audio Syscall::AudioPlaySample, Syscall::AudioPlay, // FS Syscall::FsOpen, Syscall::FsRead, Syscall::FsWrite, Syscall::FsClose, Syscall::FsListDir, Syscall::FsExists, Syscall::FsDelete, // Log Syscall::LogWrite, Syscall::LogWriteTag, // Asset/Bank Syscall::AssetLoad, Syscall::AssetStatus, Syscall::AssetCommit, Syscall::AssetCancel, Syscall::BankInfo, Syscall::BankSlotInfo, ] } #[test] fn every_syscall_has_metadata() { // 1) Every enum variant must appear in the table. for sc in all_syscalls() { let m = meta_for(*sc); assert_eq!(m.id, *sc as u32, "id mismatch for {:?}", sc); // identity fields must be present assert!(!m.module.is_empty(), "module must be non-empty for id=0x{:08X}", m.id); assert!(!m.name.is_empty(), "name must be non-empty for id=0x{:08X}", m.id); assert!(m.version > 0, "version must be > 0 for id=0x{:08X}", m.id); } // 2) Table must not contain duplicates and must map back to a valid enum. use std::collections::HashSet; let mut ids = HashSet::new(); let mut identities = HashSet::new(); for e in SYSCALL_TABLE { assert!(ids.insert(e.meta.id), "duplicate syscall id 0x{:08X}", e.meta.id); let parsed = Syscall::from_u32(e.meta.id).expect("id not recognized by enum mapping"); assert_eq!(parsed as u32, e.meta.id); // (module,name,version) must be unique let key = (e.meta.module, e.meta.name, e.meta.version); assert!(identities.insert(key), "duplicate canonical identity: ({}.{}, v{})", e.meta.module, e.meta.name, e.meta.version); } // 3) Table and explicit list sizes must match (guard against omissions). assert_eq!(SYSCALL_TABLE.len(), all_syscalls().len()); } #[test] fn resolver_returns_expected_id_for_known_identity() { // Pick a stable entry from the table let id = resolve_syscall("gfx", "clear", 1).expect("known identity must resolve"); assert_eq!(id.id, 0x1001); assert_eq!(id.meta.module, "gfx"); assert_eq!(id.meta.name, "clear"); assert_eq!(id.meta.version, 1); } #[test] fn resolver_rejects_unknown_identity() { let res = resolve_syscall("gfx", "nonexistent", 1); assert!(res.is_none()); // And via the program-level resolver let requested = [SyscallIdentity { module: "gfx", name: "nonexistent", version: 1 }]; let err = resolve_program_syscalls(&requested, 0).unwrap_err(); match err { LoadError::UnknownSyscall { module, name, version } => { assert_eq!(module, "gfx"); assert_eq!(name, "nonexistent"); assert_eq!(version, 1); } _ => panic!("expected UnknownSyscall error"), } } #[test] fn resolver_enforces_capabilities() { // Choose a syscall that requires GFX caps. let requested = [SyscallIdentity { module: "gfx", name: "clear", version: 1 }]; // Provide no caps → should fail. let err = resolve_program_syscalls(&requested, 0).unwrap_err(); match err { LoadError::MissingCapability { required, provided, module, name, version } => { assert_eq!(module, "gfx"); assert_eq!(name, "clear"); assert_eq!(version, 1); assert_ne!(required, 0); assert_eq!(provided, 0); } _ => panic!("expected MissingCapability error"), } // Provide correct caps → should pass. let ok = resolve_program_syscalls(&requested, caps::GFX).expect("must resolve with caps"); assert_eq!(ok.len(), 1); assert_eq!(ok[0].id, 0x1001); } }