pr5
This commit is contained in:
parent
b9723cfc40
commit
a7ddbcabb2
@ -129,6 +129,154 @@ pub enum Syscall {
|
||||
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,
|
||||
/// 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,
|
||||
}
|
||||
|
||||
/// 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 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, arg_slots: 0, ret_slots: 1, caps: caps::SYSTEM, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::SystemRunCart, meta: SyscallMeta { id: 0x0002, arg_slots: 0, ret_slots: 0, caps: caps::SYSTEM, determinism: Determinism::NonDeterministic } },
|
||||
|
||||
// --- GFX ---
|
||||
SyscallRegistryEntry { syscall: Syscall::GfxClear, meta: SyscallMeta { id: 0x1001, arg_slots: 1, ret_slots: 0, caps: caps::GFX, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::GfxFillRect, meta: SyscallMeta { id: 0x1002, arg_slots: 5, ret_slots: 0, caps: caps::GFX, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::GfxDrawLine, meta: SyscallMeta { id: 0x1003, arg_slots: 5, ret_slots: 0, caps: caps::GFX, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::GfxDrawCircle, meta: SyscallMeta { id: 0x1004, arg_slots: 4, ret_slots: 0, caps: caps::GFX, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::GfxDrawDisc, meta: SyscallMeta { id: 0x1005, arg_slots: 5, ret_slots: 0, caps: caps::GFX, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::GfxDrawSquare, meta: SyscallMeta { id: 0x1006, arg_slots: 6, ret_slots: 0, caps: caps::GFX, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::GfxSetSprite, meta: SyscallMeta { id: 0x1007, arg_slots: 10, ret_slots: 0, caps: caps::GFX, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::GfxDrawText, meta: SyscallMeta { id: 0x1008, arg_slots: 4, ret_slots: 0, caps: caps::GFX, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::GfxClear565, meta: SyscallMeta { id: 0x1010, arg_slots: 1, ret_slots: 0, caps: caps::GFX, determinism: Determinism::Deterministic } },
|
||||
|
||||
// --- Input ---
|
||||
SyscallRegistryEntry { syscall: Syscall::InputGetPad, meta: SyscallMeta { id: 0x2001, arg_slots: 1, ret_slots: 1, caps: caps::INPUT, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::InputGetPadPressed, meta: SyscallMeta { id: 0x2002, arg_slots: 1, ret_slots: 1, caps: caps::INPUT, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::InputGetPadReleased, meta: SyscallMeta { id: 0x2003, arg_slots: 1, ret_slots: 1, caps: caps::INPUT, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::InputGetPadHold, meta: SyscallMeta { id: 0x2004, arg_slots: 1, ret_slots: 1, caps: caps::INPUT, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::InputPadSnapshot, meta: SyscallMeta { id: 0x2010, arg_slots: 0, ret_slots: 48, caps: caps::INPUT, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::InputTouchSnapshot, meta: SyscallMeta { id: 0x2011, arg_slots: 0, ret_slots: 6, caps: caps::INPUT, determinism: Determinism::Deterministic } },
|
||||
|
||||
SyscallRegistryEntry { syscall: Syscall::TouchGetX, meta: SyscallMeta { id: 0x2101, arg_slots: 0, ret_slots: 1, caps: caps::INPUT, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::TouchGetY, meta: SyscallMeta { id: 0x2102, arg_slots: 0, ret_slots: 1, caps: caps::INPUT, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::TouchIsDown, meta: SyscallMeta { id: 0x2103, arg_slots: 0, ret_slots: 1, caps: caps::INPUT, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::TouchIsPressed, meta: SyscallMeta { id: 0x2104, arg_slots: 0, ret_slots: 1, caps: caps::INPUT, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::TouchIsReleased, meta: SyscallMeta { id: 0x2105, arg_slots: 0, ret_slots: 1, caps: caps::INPUT, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::TouchGetHold, meta: SyscallMeta { id: 0x2106, arg_slots: 0, ret_slots: 1, caps: caps::INPUT, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::TouchGetFinger, meta: SyscallMeta { id: 0x2107, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic } },
|
||||
|
||||
// --- Input (Pad service-based) ---
|
||||
SyscallRegistryEntry { syscall: Syscall::PadGetUp, meta: SyscallMeta { id: 0x2200, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::PadGetDown, meta: SyscallMeta { id: 0x2201, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::PadGetLeft, meta: SyscallMeta { id: 0x2202, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::PadGetRight, meta: SyscallMeta { id: 0x2203, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::PadGetA, meta: SyscallMeta { id: 0x2204, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::PadGetB, meta: SyscallMeta { id: 0x2205, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::PadGetX, meta: SyscallMeta { id: 0x2206, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::PadGetY, meta: SyscallMeta { id: 0x2207, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::PadGetL, meta: SyscallMeta { id: 0x2208, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::PadGetR, meta: SyscallMeta { id: 0x2209, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::PadGetStart, meta: SyscallMeta { id: 0x220A, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::PadGetSelect, meta: SyscallMeta { id: 0x220B, arg_slots: 0, ret_slots: 4, caps: caps::INPUT, determinism: Determinism::Deterministic } },
|
||||
|
||||
// --- Audio ---
|
||||
SyscallRegistryEntry { syscall: Syscall::AudioPlaySample, meta: SyscallMeta { id: 0x3001, arg_slots: 5, ret_slots: 0, caps: caps::AUDIO, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::AudioPlay, meta: SyscallMeta { id: 0x3002, arg_slots: 7, ret_slots: 0, caps: caps::AUDIO, determinism: Determinism::Deterministic } },
|
||||
|
||||
// --- FS ---
|
||||
SyscallRegistryEntry { syscall: Syscall::FsOpen, meta: SyscallMeta { id: 0x4001, arg_slots: 1, ret_slots: 1, caps: caps::FS, determinism: Determinism::NonDeterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::FsRead, meta: SyscallMeta { id: 0x4002, arg_slots: 1, ret_slots: 1, caps: caps::FS, determinism: Determinism::NonDeterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::FsWrite, meta: SyscallMeta { id: 0x4003, arg_slots: 2, ret_slots: 1, caps: caps::FS, determinism: Determinism::NonDeterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::FsClose, meta: SyscallMeta { id: 0x4004, arg_slots: 1, ret_slots: 0, caps: caps::FS, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::FsListDir, meta: SyscallMeta { id: 0x4005, arg_slots: 1, ret_slots: 1, caps: caps::FS, determinism: Determinism::NonDeterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::FsExists, meta: SyscallMeta { id: 0x4006, arg_slots: 1, ret_slots: 1, caps: caps::FS, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::FsDelete, meta: SyscallMeta { id: 0x4007, arg_slots: 1, ret_slots: 0, caps: caps::FS, determinism: Determinism::NonDeterministic } },
|
||||
|
||||
// --- Log ---
|
||||
SyscallRegistryEntry { syscall: Syscall::LogWrite, meta: SyscallMeta { id: 0x5001, arg_slots: 2, ret_slots: 0, caps: caps::LOG, determinism: Determinism::NonDeterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::LogWriteTag, meta: SyscallMeta { id: 0x5002, arg_slots: 3, ret_slots: 0, caps: caps::LOG, determinism: Determinism::NonDeterministic } },
|
||||
|
||||
// --- Asset/Bank ---
|
||||
SyscallRegistryEntry { syscall: Syscall::AssetLoad, meta: SyscallMeta { id: 0x6001, arg_slots: 3, ret_slots: 1, caps: caps::ASSET, determinism: Determinism::NonDeterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::AssetStatus, meta: SyscallMeta { id: 0x6002, arg_slots: 1, ret_slots: 1, caps: caps::ASSET, determinism: Determinism::NonDeterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::AssetCommit, meta: SyscallMeta { id: 0x6003, arg_slots: 1, ret_slots: 0, caps: caps::ASSET, determinism: Determinism::NonDeterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::AssetCancel, meta: SyscallMeta { id: 0x6004, arg_slots: 1, ret_slots: 0, caps: caps::ASSET, determinism: Determinism::NonDeterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::BankInfo, meta: SyscallMeta { id: 0x6101, arg_slots: 1, ret_slots: 1, caps: caps::BANK, determinism: Determinism::Deterministic } },
|
||||
SyscallRegistryEntry { syscall: Syscall::BankSlotInfo, meta: SyscallMeta { id: 0x6102, arg_slots: 2, ret_slots: 1, caps: caps::BANK, determinism: Determinism::Deterministic } },
|
||||
];
|
||||
|
||||
/// 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 {
|
||||
@ -190,135 +338,11 @@ impl Syscall {
|
||||
}
|
||||
|
||||
pub fn args_count(&self) -> usize {
|
||||
match self {
|
||||
Self::SystemHasCart => 0,
|
||||
Self::SystemRunCart => 0,
|
||||
Self::GfxClear => 1,
|
||||
Self::GfxFillRect => 5,
|
||||
Self::GfxDrawLine => 5,
|
||||
Self::GfxDrawCircle => 4,
|
||||
Self::GfxDrawDisc => 5,
|
||||
Self::GfxDrawSquare => 6,
|
||||
Self::GfxSetSprite => 10,
|
||||
Self::GfxDrawText => 4,
|
||||
Self::GfxClear565 => 1,
|
||||
Self::InputGetPad => 1,
|
||||
Self::InputGetPadPressed => 1,
|
||||
Self::InputGetPadReleased => 1,
|
||||
Self::InputGetPadHold => 1,
|
||||
Self::InputPadSnapshot => 0,
|
||||
Self::InputTouchSnapshot => 0,
|
||||
Self::TouchGetX => 0,
|
||||
Self::TouchGetY => 0,
|
||||
Self::TouchIsDown => 0,
|
||||
Self::TouchIsPressed => 0,
|
||||
Self::TouchIsReleased => 0,
|
||||
Self::TouchGetHold => 0,
|
||||
Self::TouchGetFinger => 0,
|
||||
Self::PadGetUp => 0,
|
||||
Self::PadGetDown => 0,
|
||||
Self::PadGetLeft => 0,
|
||||
Self::PadGetRight => 0,
|
||||
Self::PadGetA => 0,
|
||||
Self::PadGetB => 0,
|
||||
Self::PadGetX => 0,
|
||||
Self::PadGetY => 0,
|
||||
Self::PadGetL => 0,
|
||||
Self::PadGetR => 0,
|
||||
Self::PadGetStart => 0,
|
||||
Self::PadGetSelect => 0,
|
||||
Self::AudioPlaySample => 5,
|
||||
Self::AudioPlay => 7,
|
||||
Self::FsOpen => 1,
|
||||
Self::FsRead => 1,
|
||||
Self::FsWrite => 2,
|
||||
Self::FsClose => 1,
|
||||
Self::FsListDir => 1,
|
||||
Self::FsExists => 1,
|
||||
Self::FsDelete => 1,
|
||||
Self::LogWrite => 2,
|
||||
Self::LogWriteTag => 3,
|
||||
Self::AssetLoad => 3,
|
||||
Self::AssetStatus => 1,
|
||||
Self::AssetCommit => 1,
|
||||
Self::AssetCancel => 1,
|
||||
Self::BankInfo => 1,
|
||||
Self::BankSlotInfo => 2,
|
||||
}
|
||||
meta_for(*self).arg_slots as usize
|
||||
}
|
||||
|
||||
pub fn results_count(&self) -> usize {
|
||||
match self {
|
||||
// --- System ---
|
||||
Self::SystemHasCart => 1,
|
||||
Self::SystemRunCart => 0,
|
||||
|
||||
// --- GFX (void) ---
|
||||
Self::GfxClear => 0,
|
||||
Self::GfxFillRect => 0,
|
||||
Self::GfxDrawLine => 0,
|
||||
Self::GfxDrawCircle => 0,
|
||||
Self::GfxDrawDisc => 0,
|
||||
Self::GfxDrawSquare => 0,
|
||||
Self::GfxSetSprite => 0,
|
||||
Self::GfxDrawText => 0,
|
||||
Self::GfxClear565 => 0,
|
||||
|
||||
// --- Input (scalar/snapshots) ---
|
||||
Self::InputGetPad => 1,
|
||||
Self::InputGetPadPressed => 1,
|
||||
Self::InputGetPadReleased => 1,
|
||||
Self::InputGetPadHold => 1,
|
||||
Self::InputPadSnapshot => 48,
|
||||
Self::InputTouchSnapshot => 6,
|
||||
|
||||
// --- Touch (scalars/struct) ---
|
||||
Self::TouchGetX => 1,
|
||||
Self::TouchGetY => 1,
|
||||
Self::TouchIsDown => 1,
|
||||
Self::TouchIsPressed => 1,
|
||||
Self::TouchIsReleased => 1,
|
||||
Self::TouchGetHold => 1,
|
||||
Self::TouchGetFinger => 4, // Button struct (4 slots)
|
||||
|
||||
// --- Pad (per-button struct: 4 slots) ---
|
||||
Self::PadGetUp
|
||||
| Self::PadGetDown
|
||||
| Self::PadGetLeft
|
||||
| Self::PadGetRight
|
||||
| Self::PadGetA
|
||||
| Self::PadGetB
|
||||
| Self::PadGetX
|
||||
| Self::PadGetY
|
||||
| Self::PadGetL
|
||||
| Self::PadGetR
|
||||
| Self::PadGetStart
|
||||
| Self::PadGetSelect => 4,
|
||||
|
||||
// --- Audio (void) ---
|
||||
Self::AudioPlaySample => 0,
|
||||
Self::AudioPlay => 0,
|
||||
|
||||
// --- FS ---
|
||||
Self::FsOpen => 1,
|
||||
Self::FsRead => 1, // bytes read
|
||||
Self::FsWrite => 1, // bytes written
|
||||
Self::FsClose => 0,
|
||||
Self::FsListDir => 1, // entries count/handle (TBD)
|
||||
Self::FsExists => 1,
|
||||
Self::FsDelete => 0,
|
||||
|
||||
// --- Log (void) ---
|
||||
Self::LogWrite | Self::LogWriteTag => 0,
|
||||
|
||||
// --- Asset/Bank (conservador) ---
|
||||
Self::AssetLoad => 1,
|
||||
Self::AssetStatus => 1,
|
||||
Self::AssetCommit => 0,
|
||||
Self::AssetCancel => 0,
|
||||
Self::BankInfo => 1,
|
||||
Self::BankSlotInfo => 1,
|
||||
}
|
||||
meta_for(*self).ret_slots as usize
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &'static str {
|
||||
@ -379,3 +403,95 @@ impl Syscall {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
|
||||
// 2) Table must not contain duplicates and must map back to a valid enum.
|
||||
use std::collections::HashSet;
|
||||
let mut ids = 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);
|
||||
}
|
||||
|
||||
// 3) Table and explicit list sizes must match (guard against omissions).
|
||||
assert_eq!(SYSCALL_TABLE.len(), all_syscalls().len());
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,6 +80,9 @@ pub struct VirtualMachine {
|
||||
pub gc_alloc_threshold: usize,
|
||||
/// GC: snapshot of live objects count after the last collection (or VM init).
|
||||
last_gc_live_count: usize,
|
||||
/// Capability flags granted to the currently running program/cart.
|
||||
/// Syscalls are capability-gated using `prometeu_hal::syscalls::SyscallMeta::caps`.
|
||||
pub capabilities: prometeu_hal::syscalls::CapFlags,
|
||||
}
|
||||
|
||||
|
||||
@ -110,6 +113,9 @@ impl VirtualMachine {
|
||||
breakpoints: std::collections::HashSet::new(),
|
||||
gc_alloc_threshold: 1024, // conservative default; tests may override
|
||||
last_gc_live_count: 0,
|
||||
// Default to all capabilities allowed for backward compatibility.
|
||||
// Tests can narrow this via `set_capabilities`.
|
||||
capabilities: u64::MAX,
|
||||
}
|
||||
}
|
||||
|
||||
@ -131,6 +137,7 @@ impl VirtualMachine {
|
||||
self.cycles = 0;
|
||||
self.halted = true; // execution is impossible until a successful load
|
||||
self.last_gc_live_count = 0;
|
||||
// Preserve capabilities across loads; firmware may set them per cart.
|
||||
|
||||
// Only recognized format is loadable: PBS v0 industrial format
|
||||
let program = if program_bytes.starts_with(b"PBS\0") {
|
||||
@ -191,6 +198,11 @@ impl VirtualMachine {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the capability flags for the current program.
|
||||
pub fn set_capabilities(&mut self, caps: prometeu_hal::syscalls::CapFlags) {
|
||||
self.capabilities = caps;
|
||||
}
|
||||
|
||||
/// Prepares the VM to execute a specific entrypoint by setting the PC and
|
||||
/// pushing an initial call frame.
|
||||
pub fn prepare_call(&mut self, entrypoint: &str) {
|
||||
@ -912,6 +924,20 @@ impl VirtualMachine {
|
||||
)
|
||||
})?;
|
||||
|
||||
// Capability check before any side effects or argument consumption.
|
||||
let meta = prometeu_hal::syscalls::meta_for(syscall);
|
||||
if (self.capabilities & meta.caps) != meta.caps {
|
||||
return Err(self.trap(
|
||||
TRAP_INVALID_SYSCALL,
|
||||
OpCode::Syscall as u16,
|
||||
format!(
|
||||
"Missing capability for syscall {} (required=0x{:X})",
|
||||
syscall.name(), meta.caps
|
||||
),
|
||||
pc_at_syscall,
|
||||
));
|
||||
}
|
||||
|
||||
let args_count = syscall.args_count();
|
||||
|
||||
let mut args = Vec::with_capacity(args_count);
|
||||
@ -1846,6 +1872,59 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_syscall_missing_capability_trap() {
|
||||
// Program: directly call GfxClear (0x1001). We check caps before args, so no underflow.
|
||||
let rom = vec![
|
||||
0x70, 0x00, // Syscall + Reserved
|
||||
0x01, 0x10, 0x00, 0x00, // Syscall ID 0x1001 (LE)
|
||||
];
|
||||
|
||||
let mut vm = new_test_vm(rom.clone(), vec![]);
|
||||
// Remove all capabilities
|
||||
vm.set_capabilities(0);
|
||||
let mut native = MockNative;
|
||||
let mut ctx = HostContext::new(None);
|
||||
|
||||
vm.prepare_call("0");
|
||||
let report = vm.run_budget(100, &mut native, &mut ctx).unwrap();
|
||||
|
||||
match report.reason {
|
||||
LogicalFrameEndingReason::Trap(trap) => {
|
||||
assert_eq!(trap.code, TRAP_INVALID_SYSCALL);
|
||||
assert_eq!(trap.opcode, OpCode::Syscall as u16);
|
||||
assert!(trap.message.contains("Missing capability"));
|
||||
assert_eq!(trap.pc, 0);
|
||||
}
|
||||
other => panic!("Expected Trap, got {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_syscall_with_capability_success() {
|
||||
// Program: push arg 0; call GfxClear (0x1001)
|
||||
let mut rom = Vec::new();
|
||||
rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes());
|
||||
rom.extend_from_slice(&0i32.to_le_bytes());
|
||||
rom.extend_from_slice(&(OpCode::Syscall as u16).to_le_bytes());
|
||||
rom.extend_from_slice(&0x1001u32.to_le_bytes()); // GfxClear
|
||||
|
||||
let mut vm = new_test_vm(rom.clone(), vec![]);
|
||||
// Grant only GFX capability
|
||||
vm.set_capabilities(prometeu_hal::syscalls::caps::GFX);
|
||||
let mut native = MockNative;
|
||||
let mut ctx = HostContext::new(None);
|
||||
|
||||
vm.prepare_call("0");
|
||||
let report = vm.run_budget(100, &mut native, &mut ctx).unwrap();
|
||||
|
||||
// Any non-trap outcome is considered success here
|
||||
match report.reason {
|
||||
LogicalFrameEndingReason::Trap(trap) => panic!("Unexpected trap: {:?}", trap),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_syscall_results_count_mismatch_panic() {
|
||||
// GfxClear565 (0x1010) expects 0 results
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
# **Host ABI and Syscalls**
|
||||
|
||||
This chapter defines the Application Binary Interface (ABI) between the Prometeu Virtual Machine (PVM) and the host environment. It specifies how syscalls are encoded, invoked, verified, and accounted for.
|
||||
This chapter defines the Application Binary Interface (ABI) between the Prometeu Virtual Machine (PVM) and the host environment. It specifies how syscalls are identified, resolved, invoked, verified, and accounted for.
|
||||
|
||||
Syscalls provide controlled access to host-managed subsystems such as graphics, audio, input, asset banks, and persistent storage.
|
||||
|
||||
@ -20,10 +20,89 @@ The syscall system follows these rules:
|
||||
4. **Capability-gated**: Each syscall requires a declared capability.
|
||||
5. **Stack-based ABI**: Arguments and return values are passed via VM slots.
|
||||
6. **Not first-class**: Syscalls are callable but cannot be stored as values.
|
||||
7. **Language-agnostic identity**: Syscalls are identified by canonical names, not language syntax.
|
||||
|
||||
---
|
||||
|
||||
## 2 Syscall Instruction Semantics
|
||||
## 2 Canonical Syscall Identity
|
||||
|
||||
Syscalls are identified by a **canonical triple**:
|
||||
|
||||
```
|
||||
(module, name, version)
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
("gfx", "present", 1)
|
||||
("input", "state", 1)
|
||||
("audio", "play", 2)
|
||||
```
|
||||
|
||||
This identity is:
|
||||
|
||||
* Independent of language syntax.
|
||||
* Stable across languages and toolchains.
|
||||
* Used for capability checks and linking.
|
||||
|
||||
### Language independence
|
||||
|
||||
Different languages may reference the same syscall using different syntax:
|
||||
|
||||
| Language style | Reference form |
|
||||
| -------------- | --------------- |
|
||||
| Dot-based | `gfx.present` |
|
||||
| Namespace | `gfx::present` |
|
||||
| Object-style | `Gfx.present()` |
|
||||
| Functional | `present(gfx)` |
|
||||
|
||||
All of these map to the same canonical identity:
|
||||
|
||||
```
|
||||
("gfx", "present", 1)
|
||||
```
|
||||
|
||||
The compiler is responsible for this mapping.
|
||||
|
||||
---
|
||||
|
||||
## 3 Syscall Resolution
|
||||
|
||||
The host maintains a **Syscall Registry**:
|
||||
|
||||
```
|
||||
(module, name, version) -> syscall_id
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
("gfx", "present", 1) -> 0x0101
|
||||
("gfx", "submit", 1) -> 0x0102
|
||||
("audio", "play", 1) -> 0x0201
|
||||
```
|
||||
|
||||
### Resolution process
|
||||
|
||||
At cartridge load time:
|
||||
|
||||
1. The cartridge declares required syscalls using canonical identities.
|
||||
2. The host verifies capabilities.
|
||||
3. The host resolves each canonical identity to a numeric `syscall_id`.
|
||||
4. The VM stores the resolved table for fast execution.
|
||||
|
||||
At runtime, the VM executes:
|
||||
|
||||
```
|
||||
SYSCALL <id>
|
||||
```
|
||||
|
||||
Only numeric IDs are used during execution.
|
||||
|
||||
---
|
||||
|
||||
## 4 Syscall Instruction Semantics
|
||||
|
||||
The VM provides a single instruction:
|
||||
|
||||
@ -37,7 +116,7 @@ Where:
|
||||
|
||||
Execution steps:
|
||||
|
||||
1. The VM looks up the syscall metadata using `<id>`.
|
||||
1. The VM looks up syscall metadata using `<id>`.
|
||||
2. The VM verifies that enough arguments exist on the stack.
|
||||
3. The VM checks capability requirements.
|
||||
4. The syscall executes in the host environment.
|
||||
@ -47,7 +126,7 @@ If any contract rule is violated, the VM traps.
|
||||
|
||||
---
|
||||
|
||||
## 3 Syscall Metadata Table
|
||||
## 5 Syscall Metadata Table
|
||||
|
||||
Each syscall is defined by a metadata entry.
|
||||
|
||||
@ -56,7 +135,9 @@ Each syscall is defined by a metadata entry.
|
||||
```
|
||||
SyscallMeta {
|
||||
id: u32
|
||||
module: string
|
||||
name: string
|
||||
version: u16
|
||||
arg_slots: u8
|
||||
ret_slots: u8
|
||||
capability: CapabilityId
|
||||
@ -69,19 +150,21 @@ Fields:
|
||||
|
||||
| Field | Description |
|
||||
| -------------- | ------------------------------------------------ |
|
||||
| `id` | Unique syscall identifier |
|
||||
| `name` | Human-readable name |
|
||||
| `id` | Unique numeric syscall identifier |
|
||||
| `module` | Canonical module name |
|
||||
| `name` | Canonical syscall name |
|
||||
| `version` | ABI version of the syscall |
|
||||
| `arg_slots` | Number of input stack slots |
|
||||
| `ret_slots` | Number of return stack slots |
|
||||
| `capability` | Required capability |
|
||||
| `may_allocate` | Whether the syscall may allocate VM heap objects |
|
||||
| `cost_hint` | Expected cycle cost (for analysis/profiling) |
|
||||
| `cost_hint` | Expected cycle cost |
|
||||
|
||||
The verifier uses this table to validate stack effects.
|
||||
|
||||
---
|
||||
|
||||
## 4 Arguments and Return Values
|
||||
## 6 Arguments and Return Values
|
||||
|
||||
Syscalls use the same slot-based ABI as functions.
|
||||
|
||||
@ -109,21 +192,11 @@ SYSCALL input_state
|
||||
// after: [held, pressed, released]
|
||||
```
|
||||
|
||||
### Slot types
|
||||
|
||||
Each slot contains one of the VM value types:
|
||||
|
||||
* int
|
||||
* bool
|
||||
* float
|
||||
* handle
|
||||
* null
|
||||
|
||||
Composite return values are represented as multiple slots (stack tuples).
|
||||
|
||||
---
|
||||
|
||||
## 5 Syscalls as Callable Entities (Not First-Class)
|
||||
## 7 Syscalls as Callable Entities (Not First-Class)
|
||||
|
||||
Syscalls behave like functions in terms of arguments and return values, but they are **not first-class values**.
|
||||
|
||||
@ -136,7 +209,7 @@ This means:
|
||||
|
||||
Only user-defined functions and closures are first-class.
|
||||
|
||||
### Example declaration (conceptual)
|
||||
### Conceptual declaration
|
||||
|
||||
```
|
||||
host fn input_state() -> (int, int, int)
|
||||
@ -146,7 +219,7 @@ This represents a syscall with three return values, but it cannot be treated as
|
||||
|
||||
---
|
||||
|
||||
## 6 Error Model: Traps vs Status Codes
|
||||
## 8 Error Model: Traps vs Status Codes
|
||||
|
||||
Syscalls use a hybrid error model.
|
||||
|
||||
@ -155,11 +228,12 @@ Syscalls use a hybrid error model.
|
||||
The VM traps when:
|
||||
|
||||
* The syscall id is invalid.
|
||||
* The canonical identity cannot be resolved.
|
||||
* The required capability is missing.
|
||||
* The stack does not contain enough arguments.
|
||||
* A handle is invalid or dead.
|
||||
|
||||
These are considered fatal contract violations.
|
||||
These are fatal contract violations.
|
||||
|
||||
### Status returns (domain conditions)
|
||||
|
||||
@ -175,7 +249,7 @@ These are represented by status codes in return slots.
|
||||
|
||||
---
|
||||
|
||||
## 7 Capability System
|
||||
## 9 Capability System
|
||||
|
||||
Each syscall requires a capability.
|
||||
|
||||
@ -195,7 +269,7 @@ If a syscall is invoked without the required capability:
|
||||
|
||||
---
|
||||
|
||||
## 8 Interaction with the Garbage Collector
|
||||
## 10 Interaction with the Garbage Collector
|
||||
|
||||
The VM heap is managed by the GC. Host-managed memory is separate.
|
||||
|
||||
@ -220,7 +294,7 @@ This rule applies only to VM heap objects (such as closures or user objects), no
|
||||
|
||||
---
|
||||
|
||||
## 9 Determinism Rules
|
||||
## 11 Determinism Rules
|
||||
|
||||
Syscalls must obey deterministic execution rules.
|
||||
|
||||
@ -238,7 +312,7 @@ Allowed patterns:
|
||||
|
||||
---
|
||||
|
||||
## 10 Cost Model and Budgeting
|
||||
## 12 Cost Model and Budgeting
|
||||
|
||||
Each syscall contributes to frame cost.
|
||||
|
||||
@ -261,7 +335,7 @@ Nothing is free.
|
||||
|
||||
---
|
||||
|
||||
## 11 Blocking and Long Operations
|
||||
## 13 Blocking and Long Operations
|
||||
|
||||
Syscalls must not block.
|
||||
|
||||
@ -280,15 +354,19 @@ status, progress = asset.status(id)
|
||||
|
||||
---
|
||||
|
||||
## 12 Summary
|
||||
## 14 Summary
|
||||
|
||||
* Syscalls are deterministic, synchronous, and non-blocking.
|
||||
* They use the same slot-based ABI as functions.
|
||||
* They are callable but not first-class.
|
||||
* They are identified canonically by `(module, name, version)`.
|
||||
* Language syntax does not affect the ABI.
|
||||
* The host resolves canonical identities to numeric syscall IDs.
|
||||
* Capabilities control access to host subsystems.
|
||||
* GC only manages VM heap objects.
|
||||
* Host-held heap objects must be registered as roots.
|
||||
* All syscall costs are tracked per frame.
|
||||
|
||||
|
||||
|
||||
< [Back](chapter-15.md) | [Summary](table-of-contents.md) >
|
||||
157
files/TODOs.md
157
files/TODOs.md
@ -1,160 +1,3 @@
|
||||
# PR-5.1 — Define Canonical Syscall Metadata Table
|
||||
|
||||
### Briefing
|
||||
|
||||
Syscalls must follow a unified, function-like ABI based on slot counts and capabilities. This PR introduces the canonical metadata table describing every syscall.
|
||||
|
||||
### Target
|
||||
|
||||
* Define a single authoritative metadata structure for syscalls.
|
||||
* Ensure all syscalls are described in terms of slot-based ABI.
|
||||
|
||||
### Work items
|
||||
|
||||
* Introduce a `SyscallMeta` struct containing:
|
||||
|
||||
* Syscall identifier.
|
||||
* `arg_slots`.
|
||||
* `ret_slots`.
|
||||
* Capability flags.
|
||||
* Determinism flags (if defined in spec).
|
||||
* Create a canonical syscall table/registry.
|
||||
* Ensure all existing syscalls are registered in this table.
|
||||
* Document the structure in code comments (English).
|
||||
|
||||
### Acceptance checklist
|
||||
|
||||
* [ ] All syscalls have a `SyscallMeta` entry.
|
||||
* [ ] Metadata includes arg and return slot counts.
|
||||
* [ ] No syscall is invoked without metadata.
|
||||
* [ ] `cargo test` passes.
|
||||
|
||||
### Tests
|
||||
|
||||
* Add a test ensuring all registered syscalls have metadata.
|
||||
|
||||
### Junie instructions
|
||||
|
||||
**You MAY:**
|
||||
|
||||
* Introduce a syscall metadata struct.
|
||||
* Add a central registry for syscalls.
|
||||
|
||||
**You MUST NOT:**
|
||||
|
||||
* Change existing syscall semantics.
|
||||
* Add new syscalls.
|
||||
|
||||
**If unclear:**
|
||||
|
||||
* Ask before defining metadata fields.
|
||||
|
||||
---
|
||||
|
||||
# PR-5.2 — Implement Slot-Based Syscall Calling Convention
|
||||
|
||||
### Briefing
|
||||
|
||||
Syscalls must behave like functions using the stack-based slot ABI. This PR implements the unified calling convention.
|
||||
|
||||
### Target
|
||||
|
||||
* Ensure syscalls read arguments from the stack.
|
||||
* Ensure syscalls push return values based on `ret_slots`.
|
||||
|
||||
### Work items
|
||||
|
||||
* Modify the VM syscall dispatch logic to:
|
||||
|
||||
* Look up `SyscallMeta`.
|
||||
* Pop `arg_slots` from the stack.
|
||||
* Invoke the syscall.
|
||||
* Push `ret_slots` results.
|
||||
* Remove any legacy argument handling paths.
|
||||
|
||||
### Acceptance checklist
|
||||
|
||||
* [ ] All syscalls use slot-based calling convention.
|
||||
* [ ] Argument and return slot counts are enforced.
|
||||
* [ ] No legacy calling paths remain.
|
||||
* [ ] `cargo test` passes.
|
||||
|
||||
### Tests
|
||||
|
||||
* Add tests:
|
||||
|
||||
* Syscall with correct arg/ret slots → passes.
|
||||
* Syscall with insufficient args → trap or verifier failure.
|
||||
|
||||
### Junie instructions
|
||||
|
||||
**You MAY:**
|
||||
|
||||
* Refactor syscall dispatch code.
|
||||
* Use metadata to enforce slot counts.
|
||||
|
||||
**You MUST NOT:**
|
||||
|
||||
* Change the stack model.
|
||||
* Add compatibility layers.
|
||||
|
||||
**If unclear:**
|
||||
|
||||
* Ask before modifying dispatch semantics.
|
||||
|
||||
---
|
||||
|
||||
# PR-5.3 — Enforce Capability Checks in Syscall Dispatch
|
||||
|
||||
### Briefing
|
||||
|
||||
Syscalls must be capability-gated. The runtime must verify that the current program has permission to invoke each syscall.
|
||||
|
||||
### Target
|
||||
|
||||
* Enforce capability checks before syscall execution.
|
||||
|
||||
### Work items
|
||||
|
||||
* Extend `SyscallMeta` with capability requirements.
|
||||
* Add capability data to the VM or execution context.
|
||||
* In syscall dispatch:
|
||||
|
||||
* Check capabilities.
|
||||
* If missing, trigger `TRAP_INVALID_SYSCALL` or equivalent.
|
||||
|
||||
### Acceptance checklist
|
||||
|
||||
* [ ] Capability checks occur before syscall execution.
|
||||
* [ ] Missing capability leads to deterministic trap.
|
||||
* [ ] Valid capabilities allow execution.
|
||||
* [ ] `cargo test` passes.
|
||||
|
||||
### Tests
|
||||
|
||||
* Add tests:
|
||||
|
||||
* Syscall without capability → trap.
|
||||
* Syscall with capability → success.
|
||||
|
||||
### Junie instructions
|
||||
|
||||
**You MAY:**
|
||||
|
||||
* Add capability checks in dispatch.
|
||||
* Extend metadata with capability fields.
|
||||
|
||||
**You MUST NOT:**
|
||||
|
||||
* Change trap semantics.
|
||||
* Introduce dynamic or nondeterministic permission systems.
|
||||
|
||||
**If unclear:**
|
||||
|
||||
* Ask before defining capability behavior.
|
||||
|
||||
---
|
||||
|
||||
# PR-5.4 — Verifier Integration for Syscall Slot Rules
|
||||
|
||||
### Briefing
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user