654 lines
35 KiB
Rust
654 lines
35 KiB
Rust
/// 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 <id>` 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<SyscallResolved> {
|
|
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<Vec<SyscallResolved>, 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<Self> {
|
|
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);
|
|
}
|
|
}
|