From d5ef8a2003f807d9e9ff17e0de634881df81ee8c Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Mon, 2 Mar 2026 14:02:05 +0000 Subject: [PATCH] adjustments on capabilities on cartridges --- crates/console/prometeu-bytecode/src/lib.rs | 8 +- .../tests/disasm_roundtrip.rs | 6 +- .../tests/disasm_snapshot.rs | 4 +- crates/console/prometeu-hal/src/cartridge.rs | 21 + .../prometeu-hal/src/cartridge_loader.rs | 161 +++- crates/console/prometeu-hal/src/syscalls.rs | 883 ++++++++++++++++-- crates/console/prometeu-vm/src/heap.rs | 95 +- crates/console/prometeu-vm/src/lib.rs | 8 +- crates/console/prometeu-vm/src/object.rs | 14 +- crates/console/prometeu-vm/src/roots.rs | 8 +- crates/console/prometeu-vm/src/scheduler.rs | 56 +- .../tests/bytecode_encode_decode.rs | 7 +- .../tests/gc_collect_unreachable.rs | 2 +- .../tests/scheduler_determinism.rs | 6 +- .../tests/vm_exec_valid.rs | 6 +- .../tests/no_legacy.rs | 130 ++- crates/tools/pbxgen-stress/src/lib.rs | 32 +- crates/tools/pbxgen-stress/src/main.rs | 4 +- ...- PBX SYSC and HOSTCALL Loader Patching.md | 324 +++++++ 19 files changed, 1570 insertions(+), 205 deletions(-) create mode 100644 docs/pull-requests/PR-2 - PBX SYSC and HOSTCALL Loader Patching.md diff --git a/crates/console/prometeu-bytecode/src/lib.rs b/crates/console/prometeu-bytecode/src/lib.rs index f0d3dd9f..1e815bd5 100644 --- a/crates/console/prometeu-bytecode/src/lib.rs +++ b/crates/console/prometeu-bytecode/src/lib.rs @@ -1,22 +1,22 @@ mod abi; +pub mod assembler; mod decoder; +mod disassembler; +pub mod isa; // canonical ISA boundary (core and future profiles) mod layout; pub mod model; mod opcode; mod opcode_spec; mod program_image; mod value; -pub mod isa; // canonical ISA boundary (core and future profiles) -mod disassembler; -pub mod assembler; pub use abi::{ TrapInfo, TRAP_BAD_RET_SLOTS, TRAP_DIV_ZERO, TRAP_ILLEGAL_INSTRUCTION, TRAP_INVALID_FUNC, TRAP_INVALID_LOCAL, TRAP_INVALID_SYSCALL, TRAP_OOB, TRAP_STACK_UNDERFLOW, TRAP_TYPE, }; +pub use assembler::{assemble, AsmError}; pub use decoder::{decode_next, DecodeError}; pub use disassembler::disassemble; -pub use assembler::{assemble, AsmError}; pub use layout::{compute_function_layouts, FunctionLayout}; pub use model::{BytecodeLoader, FunctionMeta, LoadError}; pub use program_image::ProgramImage; diff --git a/crates/console/prometeu-bytecode/tests/disasm_roundtrip.rs b/crates/console/prometeu-bytecode/tests/disasm_roundtrip.rs index 645ca12a..4dcb6fb0 100644 --- a/crates/console/prometeu-bytecode/tests/disasm_roundtrip.rs +++ b/crates/console/prometeu-bytecode/tests/disasm_roundtrip.rs @@ -1,9 +1,11 @@ -use prometeu_bytecode::{assemble, disassemble}; use prometeu_bytecode::isa::core::CoreOpCode; +use prometeu_bytecode::{assemble, disassemble}; fn emit(op: CoreOpCode, imm: Option<&[u8]>, out: &mut Vec) { out.extend_from_slice(&(op as u16).to_le_bytes()); - if let Some(bytes) = imm { out.extend_from_slice(bytes); } + if let Some(bytes) = imm { + out.extend_from_slice(bytes); + } } #[test] diff --git a/crates/console/prometeu-bytecode/tests/disasm_snapshot.rs b/crates/console/prometeu-bytecode/tests/disasm_snapshot.rs index c15054e0..cf2341f1 100644 --- a/crates/console/prometeu-bytecode/tests/disasm_snapshot.rs +++ b/crates/console/prometeu-bytecode/tests/disasm_snapshot.rs @@ -3,7 +3,9 @@ use prometeu_bytecode::isa::core::CoreOpCode; fn emit(op: CoreOpCode, imm: Option<&[u8]>, out: &mut Vec) { out.extend_from_slice(&(op as u16).to_le_bytes()); - if let Some(bytes) = imm { out.extend_from_slice(bytes); } + if let Some(bytes) = imm { + out.extend_from_slice(bytes); + } } #[test] diff --git a/crates/console/prometeu-hal/src/cartridge.rs b/crates/console/prometeu-hal/src/cartridge.rs index fd3b4ae7..d850645b 100644 --- a/crates/console/prometeu-hal/src/cartridge.rs +++ b/crates/console/prometeu-hal/src/cartridge.rs @@ -1,4 +1,5 @@ use crate::asset::{AssetEntry, PreloadEntry}; +use crate::syscalls::CapFlags; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] @@ -14,6 +15,7 @@ pub struct Cartridge { pub app_version: String, pub app_mode: AppMode, pub entrypoint: String, + pub capabilities: CapFlags, pub program: Vec, pub assets: Vec, pub asset_table: Vec, @@ -27,6 +29,7 @@ pub struct CartridgeDTO { pub app_version: String, pub app_mode: AppMode, pub entrypoint: String, + pub capabilities: CapFlags, pub program: Vec, pub assets: Vec, #[serde(default)] @@ -43,6 +46,7 @@ impl From for Cartridge { app_version: dto.app_version, app_mode: dto.app_mode, entrypoint: dto.entrypoint, + capabilities: dto.capabilities, program: dto.program, assets: dto.assets, asset_table: dto.asset_table, @@ -61,6 +65,21 @@ pub enum CartridgeError { IoError, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum Capability { + None, + System, + Gfx, + Input, + Audio, + Fs, + Log, + Asset, + Bank, + All, +} + #[derive(Deserialize)] pub struct CartridgeManifest { pub magic: String, @@ -71,6 +90,8 @@ pub struct CartridgeManifest { pub app_mode: AppMode, pub entrypoint: String, #[serde(default)] + pub capabilities: Vec, + #[serde(default)] pub asset_table: Vec, #[serde(default)] pub preload: Vec, diff --git a/crates/console/prometeu-hal/src/cartridge_loader.rs b/crates/console/prometeu-hal/src/cartridge_loader.rs index da773018..babf88a9 100644 --- a/crates/console/prometeu-hal/src/cartridge_loader.rs +++ b/crates/console/prometeu-hal/src/cartridge_loader.rs @@ -1,4 +1,6 @@ -use crate::cartridge::{Cartridge, CartridgeDTO, CartridgeError, CartridgeManifest}; +use crate::cartridge::{Capability, Cartridge, CartridgeDTO, CartridgeError, CartridgeManifest}; +use crate::syscalls::{CapFlags, caps}; +use std::collections::HashSet; use std::fs; use std::path::Path; @@ -43,6 +45,8 @@ impl DirectoryCartridgeLoader { return Err(CartridgeError::UnsupportedVersion); } + let capabilities = normalize_capabilities(&manifest.capabilities)?; + let program_path = path.join("program.pbx"); if !program_path.exists() { return Err(CartridgeError::MissingProgram); @@ -63,6 +67,7 @@ impl DirectoryCartridgeLoader { app_version: manifest.app_version, app_mode: manifest.app_mode, entrypoint: manifest.entrypoint, + capabilities, program, assets, asset_table: manifest.asset_table, @@ -80,3 +85,157 @@ impl PackedCartridgeLoader { Err(CartridgeError::InvalidFormat) } } + +fn normalize_capabilities(capabilities: &[Capability]) -> Result { + let mut seen = HashSet::new(); + let mut normalized = caps::NONE; + + for capability in capabilities { + if !seen.insert(*capability) { + return Err(CartridgeError::InvalidManifest); + } + + normalized |= match capability { + Capability::None => caps::NONE, + Capability::System => caps::SYSTEM, + Capability::Gfx => caps::GFX, + Capability::Input => caps::INPUT, + Capability::Audio => caps::AUDIO, + Capability::Fs => caps::FS, + Capability::Log => caps::LOG, + Capability::Asset => caps::ASSET, + Capability::Bank => caps::BANK, + Capability::All => caps::ALL, + }; + } + + Ok(normalized) +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + use std::path::{Path, PathBuf}; + use std::sync::atomic::{AtomicU64, Ordering}; + use std::time::{SystemTime, UNIX_EPOCH}; + + static TEST_DIR_COUNTER: AtomicU64 = AtomicU64::new(0); + + struct TestCartridgeDir { + path: PathBuf, + } + + impl TestCartridgeDir { + fn new(manifest: serde_json::Value) -> Self { + let unique = TEST_DIR_COUNTER.fetch_add(1, Ordering::Relaxed); + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("system time must be after unix epoch") + .as_nanos(); + let path = std::env::temp_dir() + .join(format!("prometeu-hal-cartridge-loader-{}-{}", timestamp, unique)); + + fs::create_dir_all(&path).expect("must create temporary cartridge directory"); + fs::write( + path.join("manifest.json"), + serde_json::to_vec_pretty(&manifest).expect("manifest must serialize"), + ) + .expect("must write manifest.json"); + fs::write(path.join("program.pbx"), [0x01_u8, 0x02, 0x03]).expect("must write program"); + + Self { path } + } + + fn path(&self) -> &Path { + &self.path + } + } + + impl Drop for TestCartridgeDir { + fn drop(&mut self) { + let _ = fs::remove_dir_all(&self.path); + } + } + + fn manifest_with_capabilities(capabilities: Option>) -> serde_json::Value { + let mut manifest = json!({ + "magic": "PMTU", + "cartridge_version": 1, + "app_id": 1001, + "title": "Example", + "app_version": "1.0.0", + "app_mode": "Game", + "entrypoint": "main" + }); + + if let Some(capabilities) = capabilities { + manifest["capabilities"] = json!(capabilities); + } + + manifest + } + + #[test] + fn load_without_capabilities_defaults_to_none() { + let dir = TestCartridgeDir::new(manifest_with_capabilities(None)); + + let cartridge = DirectoryCartridgeLoader::load(dir.path()).expect("cartridge must load"); + + assert_eq!(cartridge.capabilities, caps::NONE); + } + + #[test] + fn load_with_single_capability_normalizes_to_flag() { + let dir = TestCartridgeDir::new(manifest_with_capabilities(Some(vec!["gfx"]))); + + let cartridge = DirectoryCartridgeLoader::load(dir.path()).expect("cartridge must load"); + + assert_eq!(cartridge.capabilities, caps::GFX); + } + + #[test] + fn load_with_multiple_capabilities_combines_flags() { + let dir = TestCartridgeDir::new(manifest_with_capabilities(Some(vec!["gfx", "input"]))); + + let cartridge = DirectoryCartridgeLoader::load(dir.path()).expect("cartridge must load"); + + assert_eq!(cartridge.capabilities, caps::GFX | caps::INPUT); + } + + #[test] + fn load_with_all_capability_normalizes_to_all_flags() { + let dir = TestCartridgeDir::new(manifest_with_capabilities(Some(vec!["all"]))); + + let cartridge = DirectoryCartridgeLoader::load(dir.path()).expect("cartridge must load"); + + assert_eq!(cartridge.capabilities, caps::ALL); + } + + #[test] + fn load_with_none_capability_keeps_zero_flags() { + let dir = TestCartridgeDir::new(manifest_with_capabilities(Some(vec!["none"]))); + + let cartridge = DirectoryCartridgeLoader::load(dir.path()).expect("cartridge must load"); + + assert_eq!(cartridge.capabilities, caps::NONE); + } + + #[test] + fn load_with_duplicate_capabilities_fails() { + let dir = TestCartridgeDir::new(manifest_with_capabilities(Some(vec!["gfx", "gfx"]))); + + let error = DirectoryCartridgeLoader::load(dir.path()).unwrap_err(); + + assert!(matches!(error, CartridgeError::InvalidManifest)); + } + + #[test] + fn load_with_unknown_capability_fails() { + let dir = TestCartridgeDir::new(manifest_with_capabilities(Some(vec!["network"]))); + + let error = DirectoryCartridgeLoader::load(dir.path()).unwrap_err(); + + assert!(matches!(error, CartridgeError::InvalidManifest)); + } +} diff --git a/crates/console/prometeu-hal/src/syscalls.rs b/crates/console/prometeu-hal/src/syscalls.rs index d49be99d..0b244f56 100644 --- a/crates/console/prometeu-hal/src/syscalls.rs +++ b/crates/console/prometeu-hal/src/syscalls.rs @@ -226,11 +226,7 @@ pub struct SyscallResolved { #[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, - }, + UnknownSyscall { module: &'static str, name: &'static str, version: u16 }, /// The cartridge lacks required capabilities for the syscall. MissingCapability { required: CapFlags, @@ -244,7 +240,11 @@ pub enum LoadError { /// 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 { +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 }); @@ -266,7 +266,11 @@ pub fn resolve_program_syscalls( 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 }); + 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; @@ -291,74 +295,808 @@ pub fn resolve_program_syscalls( /// 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 } }, - + 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 } }, - + 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 } }, - + 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 } }, - + 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 } }, - + 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 } }, - + 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 } }, - + 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 } }, + 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. @@ -593,8 +1331,13 @@ mod tests { // (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); + 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). diff --git a/crates/console/prometeu-vm/src/heap.rs b/crates/console/prometeu-vm/src/heap.rs index 9622f1ff..83075322 100644 --- a/crates/console/prometeu-vm/src/heap.rs +++ b/crates/console/prometeu-vm/src/heap.rs @@ -1,5 +1,5 @@ -use crate::object::{ObjectHeader, ObjectKind}; use crate::call_frame::CallFrame; +use crate::object::{ObjectHeader, ObjectKind}; use prometeu_bytecode::{HeapRef, Value}; /// Internal stored object: header plus opaque payload bytes. @@ -50,14 +50,22 @@ pub struct Heap { } impl Heap { - pub fn new() -> Self { Self { objects: Vec::new() } } + pub fn new() -> Self { + Self { objects: Vec::new() } + } /// Allocate a new object with the given kind and raw payload bytes. /// Returns an opaque `HeapRef` handle. #[cfg(test)] pub fn allocate_object(&mut self, kind: ObjectKind, payload: &[u8]) -> HeapRef { let header = ObjectHeader::new(kind, payload.len() as u32); - let obj = StoredObject { header, payload: payload.to_vec(), array_elems: None, closure_env: None, coroutine: None }; + let obj = StoredObject { + header, + payload: payload.to_vec(), + array_elems: None, + closure_env: None, + coroutine: None, + }; let idx = self.objects.len(); // No free-list reuse in this PR: append and keep indices stable. self.objects.push(Some(obj)); @@ -69,7 +77,13 @@ impl Heap { #[cfg(test)] pub fn allocate_array(&mut self, elements: Vec) -> HeapRef { let header = ObjectHeader::new(ObjectKind::Array, elements.len() as u32); - let obj = StoredObject { header, payload: Vec::new(), array_elems: Some(elements), closure_env: None, coroutine: None }; + let obj = StoredObject { + header, + payload: Vec::new(), + array_elems: Some(elements), + closure_env: None, + coroutine: None, + }; let idx = self.objects.len(); // No free-list reuse in this PR: append and keep indices stable. self.objects.push(Some(obj)); @@ -125,7 +139,9 @@ impl Heap { /// Returns true if this handle refers to an allocated object. pub fn is_valid(&self, r: HeapRef) -> bool { let idx = r.0 as usize; - if idx >= self.objects.len() { return false; } + if idx >= self.objects.len() { + return false; + } self.objects[idx].is_some() } @@ -147,18 +163,12 @@ impl Heap { /// Get immutable access to an object's header by handle. pub fn header(&self, r: HeapRef) -> Option<&ObjectHeader> { - self.objects - .get(r.0 as usize) - .and_then(|slot| slot.as_ref()) - .map(|o| &o.header) + self.objects.get(r.0 as usize).and_then(|slot| slot.as_ref()).map(|o| &o.header) } /// Internal: get mutable access to an object's header by handle. fn header_mut(&mut self, r: HeapRef) -> Option<&mut ObjectHeader> { - self.objects - .get_mut(r.0 as usize) - .and_then(|slot| slot.as_mut()) - .map(|o| &mut o.header) + self.objects.get_mut(r.0 as usize).and_then(|slot| slot.as_mut()).map(|o| &mut o.header) } // Internal: list inner `HeapRef` children of an object without allocating. @@ -215,8 +225,12 @@ impl Heap { pub fn closure_fn_id(&self, r: HeapRef) -> Option { let idx = r.0 as usize; let slot = self.objects.get(idx)?.as_ref()?; - if slot.header.kind != ObjectKind::Closure { return None; } - if slot.payload.len() < 8 { return None; } + if slot.header.kind != ObjectKind::Closure { + return None; + } + if slot.payload.len() < 8 { + return None; + } debug_assert_eq!(slot.header.payload_len, 8); let mut bytes = [0u8; 4]; bytes.copy_from_slice(&slot.payload[0..4]); @@ -228,7 +242,9 @@ impl Heap { pub fn closure_env_slice(&self, r: HeapRef) -> Option<&[Value]> { let idx = r.0 as usize; let slot = self.objects.get(idx)?.as_ref()?; - if slot.header.kind != ObjectKind::Closure { return None; } + if slot.header.kind != ObjectKind::Closure { + return None; + } if slot.payload.len() >= 8 { let mut nbytes = [0u8; 4]; nbytes.copy_from_slice(&slot.payload[4..8]); @@ -246,17 +262,21 @@ impl Heap { let mut stack: Vec = roots.into_iter().collect(); while let Some(r) = stack.pop() { - if !self.is_valid(r) { continue; } + if !self.is_valid(r) { + continue; + } // If already marked, skip. - let already_marked = self - .header(r) - .map(|h: &ObjectHeader| h.is_marked()) - .unwrap_or(false); - if already_marked { continue; } + let already_marked = + self.header(r).map(|h: &ObjectHeader| h.is_marked()).unwrap_or(false); + if already_marked { + continue; + } // Set mark bit. - if let Some(h) = self.header_mut(r) { h.set_marked(true); } + if let Some(h) = self.header_mut(r) { + h.set_marked(true); + } // Push children by scanning payload directly (no intermediate Vec allocs). let idx = r.0 as usize; @@ -272,7 +292,9 @@ impl Heap { .header(*child) .map(|h: &ObjectHeader| h.is_marked()) .unwrap_or(false); - if !marked { stack.push(*child); } + if !marked { + stack.push(*child); + } } } } @@ -283,7 +305,11 @@ impl Heap { nbytes.copy_from_slice(&obj.payload[4..8]); let env_len = u32::from_le_bytes(nbytes) as usize; if let Some(env) = obj.closure_env.as_ref() { - debug_assert_eq!(env.len(), env_len, "closure env len must match encoded env_len"); + debug_assert_eq!( + env.len(), + env_len, + "closure env len must match encoded env_len" + ); for val in env[..env_len].iter() { if let Value::HeapRef(child) = val && self.is_valid(*child) @@ -292,7 +318,9 @@ impl Heap { .header(*child) .map(|h: &ObjectHeader| h.is_marked()) .unwrap_or(false); - if !marked { stack.push(*child); } + if !marked { + stack.push(*child); + } } } } @@ -307,7 +335,9 @@ impl Heap { .header(*child) .map(|h: &ObjectHeader| h.is_marked()) .unwrap_or(false); - if !marked { stack.push(*child); } + if !marked { + stack.push(*child); + } } } } @@ -336,7 +366,9 @@ impl Heap { } /// Current number of allocated (live) objects. - pub fn len(&self) -> usize { self.objects.iter().filter(|s| s.is_some()).count() } + pub fn len(&self) -> usize { + self.objects.iter().filter(|s| s.is_some()).count() + } /// Enumerate handles of coroutines that are currently suspended (i.e., not running): /// Ready or Sleeping. These must be treated as GC roots by the runtime so their @@ -430,11 +462,8 @@ mod tests { // Target object B (unreferenced yet) let b = heap.allocate_object(ObjectKind::Bytes, &[9, 9, 9]); // Array A that contains a reference to B among other primitives - let a = heap.allocate_array(vec![ - Value::Int32(1), - Value::HeapRef(b), - Value::Boolean(false), - ]); + let a = + heap.allocate_array(vec![Value::Int32(1), Value::HeapRef(b), Value::Boolean(false)]); // Mark starting from root A heap.mark_from_roots([a]); diff --git a/crates/console/prometeu-vm/src/lib.rs b/crates/console/prometeu-vm/src/lib.rs index 11c89bb3..e03c6243 100644 --- a/crates/console/prometeu-vm/src/lib.rs +++ b/crates/console/prometeu-vm/src/lib.rs @@ -2,16 +2,16 @@ mod call_frame; mod local_addressing; // Keep the verifier internal in production builds, but expose it for integration tests // so the golden verifier suite can exercise it without widening the public API in releases. +mod heap; +mod object; +mod roots; +mod scheduler; #[cfg(not(test))] mod verifier; #[cfg(test)] mod verifier; mod virtual_machine; mod vm_init_error; -mod object; -mod heap; -mod roots; -mod scheduler; pub use prometeu_hal::{HostContext, HostReturn, NativeInterface, SyscallId}; pub use virtual_machine::{BudgetReport, LogicalFrameEndingReason, VirtualMachine}; diff --git a/crates/console/prometeu-vm/src/object.rs b/crates/console/prometeu-vm/src/object.rs index 1ad01be9..7f0260f5 100644 --- a/crates/console/prometeu-vm/src/object.rs +++ b/crates/console/prometeu-vm/src/object.rs @@ -51,7 +51,8 @@ #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub enum ObjectKind { /// Reserved/unknown kind. Should not appear in valid allocations. - #[allow(dead_code)] // Kept for stable tag layout and persisted images, even if not constructed in this crate yet + #[allow(dead_code)] + // Kept for stable tag layout and persisted images, even if not constructed in this crate yet Unknown = 0, /// UTF-8 string. `payload_len` is the number of bytes. @@ -87,7 +88,6 @@ pub enum ObjectKind { /// contained `HeapRef`s directly. /// - `payload_len` is 0 for this fixed-layout object. Coroutine = 6, - // Future kinds must be appended here to keep tag numbers stable. } @@ -120,10 +120,16 @@ impl ObjectHeader { } /// Returns true if the GC mark bit is set. - pub fn is_marked(&self) -> bool { (self.flags & object_flags::MARKED) != 0 } + pub fn is_marked(&self) -> bool { + (self.flags & object_flags::MARKED) != 0 + } /// Sets or clears the GC mark bit. Note: actual GC logic lives elsewhere. pub fn set_marked(&mut self, value: bool) { - if value { self.flags |= object_flags::MARKED; } else { self.flags &= !object_flags::MARKED; } + if value { + self.flags |= object_flags::MARKED; + } else { + self.flags &= !object_flags::MARKED; + } } } diff --git a/crates/console/prometeu-vm/src/roots.rs b/crates/console/prometeu-vm/src/roots.rs index b4e76b94..4a69cd39 100644 --- a/crates/console/prometeu-vm/src/roots.rs +++ b/crates/console/prometeu-vm/src/roots.rs @@ -18,9 +18,13 @@ mod tests { use super::*; use crate::VirtualMachine; - struct CollectVisitor { pub seen: Vec } + struct CollectVisitor { + pub seen: Vec, + } impl RootVisitor for CollectVisitor { - fn visit_heap_ref(&mut self, r: HeapRef) { self.seen.push(r); } + fn visit_heap_ref(&mut self, r: HeapRef) { + self.seen.push(r); + } } #[test] diff --git a/crates/console/prometeu-vm/src/scheduler.rs b/crates/console/prometeu-vm/src/scheduler.rs index 58c2cdf6..4fdde450 100644 --- a/crates/console/prometeu-vm/src/scheduler.rs +++ b/crates/console/prometeu-vm/src/scheduler.rs @@ -28,7 +28,9 @@ struct SleepEntry { } impl Scheduler { - pub fn new() -> Self { Self::default() } + pub fn new() -> Self { + Self::default() + } // ---------- Ready queue operations ---------- @@ -43,15 +45,25 @@ impl Scheduler { } #[cfg(test)] - pub fn is_ready_empty(&self) -> bool { self.ready_queue.is_empty() } + pub fn is_ready_empty(&self) -> bool { + self.ready_queue.is_empty() + } #[cfg(test)] - pub fn ready_len(&self) -> usize { self.ready_queue.len() } + pub fn ready_len(&self) -> usize { + self.ready_queue.len() + } // ---------- Current tracking (no switching here) ---------- - pub fn set_current(&mut self, coro: Option) { self.current = coro; } + pub fn set_current(&mut self, coro: Option) { + self.current = coro; + } #[cfg(test)] - pub fn current(&self) -> Option { self.current } - pub fn clear_current(&mut self) { self.current = None; } + pub fn current(&self) -> Option { + self.current + } + pub fn clear_current(&mut self) { + self.current = None; + } // ---------- Sleeping operations ---------- @@ -63,15 +75,13 @@ impl Scheduler { self.next_seq = self.next_seq.wrapping_add(1); // Binary search insertion point by wake_tick, then by seq to keep total order deterministic - let idx = match self - .sleeping - .binary_search_by(|e| { - if e.wake_tick == entry.wake_tick { - e.seq.cmp(&entry.seq) - } else { - e.wake_tick.cmp(&entry.wake_tick) - } - }) { + let idx = match self.sleeping.binary_search_by(|e| { + if e.wake_tick == entry.wake_tick { + e.seq.cmp(&entry.seq) + } else { + e.wake_tick.cmp(&entry.wake_tick) + } + }) { Ok(i) => i, // equal element position; insert after to preserve FIFO among equals Err(i) => i, }; @@ -81,10 +91,10 @@ impl Scheduler { /// Move all sleeping coroutines with `wake_tick <= current_tick` to ready queue (FIFO by wake order). pub fn wake_ready(&mut self, current_tick: u64) { // Find split point where wake_tick > current_tick - let split = self - .sleeping - .partition_point(|e| e.wake_tick <= current_tick); - if split == 0 { return; } + let split = self.sleeping.partition_point(|e| e.wake_tick <= current_tick); + if split == 0 { + return; + } let mut ready_slice: Vec = self.sleeping.drain(0..split).collect(); // Already in order by (wake_tick, seq); push in that order to preserve determinism for e in ready_slice.drain(..) { @@ -93,14 +103,18 @@ impl Scheduler { } /// Returns true if there are any sleeping coroutines. - pub fn has_sleeping(&self) -> bool { !self.sleeping.is_empty() } + pub fn has_sleeping(&self) -> bool { + !self.sleeping.is_empty() + } } #[cfg(test)] mod tests { use super::*; - fn hr(id: u32) -> HeapRef { HeapRef(id) } + fn hr(id: u32) -> HeapRef { + HeapRef(id) + } #[test] fn fifo_ready_queue_is_deterministic() { diff --git a/crates/dev/prometeu-layer-tests/tests/bytecode_encode_decode.rs b/crates/dev/prometeu-layer-tests/tests/bytecode_encode_decode.rs index ed03e645..39bc29ca 100644 --- a/crates/dev/prometeu-layer-tests/tests/bytecode_encode_decode.rs +++ b/crates/dev/prometeu-layer-tests/tests/bytecode_encode_decode.rs @@ -8,10 +8,9 @@ fn emit(op: CoreOpCode, imm: Option<&[u8]>, out: &mut Vec) { match (need, imm) { (0, None) => {} (n, Some(bytes)) if bytes.len() == n => out.extend_from_slice(bytes), - (n, Some(bytes)) => panic!( - "immediate size mismatch for {:?}: expected {}, got {}", - op, n, bytes.len() - ), + (n, Some(bytes)) => { + panic!("immediate size mismatch for {:?}: expected {}, got {}", op, n, bytes.len()) + } (n, None) => panic!("missing immediate for {:?}: need {} bytes", op, n), } } diff --git a/crates/dev/prometeu-layer-tests/tests/gc_collect_unreachable.rs b/crates/dev/prometeu-layer-tests/tests/gc_collect_unreachable.rs index bf581d49..3a20116f 100644 --- a/crates/dev/prometeu-layer-tests/tests/gc_collect_unreachable.rs +++ b/crates/dev/prometeu-layer-tests/tests/gc_collect_unreachable.rs @@ -1,6 +1,6 @@ +use prometeu_hal::vm_fault::VmFault; use prometeu_vm::VirtualMachine; use prometeu_vm::{HostContext, HostReturn, NativeInterface, SyscallId}; -use prometeu_hal::vm_fault::VmFault; #[test] fn gc_collects_unreachable_closure_but_keeps_marked() { diff --git a/crates/dev/prometeu-layer-tests/tests/scheduler_determinism.rs b/crates/dev/prometeu-layer-tests/tests/scheduler_determinism.rs index 80e2dc7e..950bee97 100644 --- a/crates/dev/prometeu-layer-tests/tests/scheduler_determinism.rs +++ b/crates/dev/prometeu-layer-tests/tests/scheduler_determinism.rs @@ -1,6 +1,6 @@ use prometeu_bytecode::isa::core::{CoreOpCode, CoreOpCodeSpecExt}; -use prometeu_vm::{VirtualMachine, BudgetReport, LogicalFrameEndingReason}; use prometeu_hal::{HostContext, HostReturn, NativeInterface, SyscallId}; +use prometeu_vm::{BudgetReport, LogicalFrameEndingReason, VirtualMachine}; #[test] fn scheduler_wake_and_ready_order_is_deterministic() { @@ -13,7 +13,9 @@ fn scheduler_wake_and_ready_order_is_deterministic() { match (need, imm) { (0, None) => {} (n, Some(bytes)) if bytes.len() == n => out.extend_from_slice(bytes), - (n, Some(bytes)) => panic!("imm size mismatch for {:?}: need {}, got {}", op, n, bytes.len()), + (n, Some(bytes)) => { + panic!("imm size mismatch for {:?}: need {}, got {}", op, n, bytes.len()) + } (n, None) => panic!("missing imm for {:?}: need {} bytes", op, n), } } diff --git a/crates/dev/prometeu-layer-tests/tests/vm_exec_valid.rs b/crates/dev/prometeu-layer-tests/tests/vm_exec_valid.rs index 3f2905aa..0311d8e9 100644 --- a/crates/dev/prometeu-layer-tests/tests/vm_exec_valid.rs +++ b/crates/dev/prometeu-layer-tests/tests/vm_exec_valid.rs @@ -1,7 +1,7 @@ use prometeu_bytecode::isa::core::{CoreOpCode, CoreOpCodeSpecExt}; +use prometeu_bytecode::Value; use prometeu_hal::{HostContext, HostReturn, NativeInterface, SyscallId}; use prometeu_vm::{BudgetReport, LogicalFrameEndingReason, VirtualMachine}; -use prometeu_bytecode::Value; fn emit(op: CoreOpCode, imm: Option<&[u8]>, out: &mut Vec) { out.extend_from_slice(&(op as u16).to_le_bytes()); @@ -9,7 +9,9 @@ fn emit(op: CoreOpCode, imm: Option<&[u8]>, out: &mut Vec) { match (need, imm) { (0, None) => {} (n, Some(bytes)) if bytes.len() == n => out.extend_from_slice(bytes), - (n, Some(bytes)) => panic!("imm size mismatch for {:?}: need {}, got {}", op, n, bytes.len()), + (n, Some(bytes)) => { + panic!("imm size mismatch for {:?}: need {}, got {}", op, n, bytes.len()) + } (n, None) => panic!("missing imm for {:?}: need {} bytes", op, n), } } diff --git a/crates/dev/prometeu-quality-checks/tests/no_legacy.rs b/crates/dev/prometeu-quality-checks/tests/no_legacy.rs index 5659bb16..68ef76db 100644 --- a/crates/dev/prometeu-quality-checks/tests/no_legacy.rs +++ b/crates/dev/prometeu-quality-checks/tests/no_legacy.rs @@ -7,11 +7,9 @@ const FORBIDDEN_IDENT_TOKENS: &[&str] = &[ // HIP must be fully removed from code "hip", "HIP", - // Legacy RC API surface (exact token only) "release", "Release", - // Legacy scope helpers (exact tokens only) "enter_scope", "exit_scope", @@ -22,7 +20,6 @@ const FORBIDDEN_IDENT_TOKENS: &[&str] = &[ "borrow_scope", "mutate_scope", "peek_scope", - // Legacy handle/gate/retain-release naming that is almost certainly RC/HIP-related "GateHandle", "gate_handle", @@ -48,7 +45,8 @@ const FORBIDDEN_PATH_SEGMENTS: &[&str] = &[ #[test] fn test_no_legacy_artifacts() { - let workspace_root = find_workspace_root().expect("Failed to locate workspace root with [workspace] Cargo.toml"); + let workspace_root = + find_workspace_root().expect("Failed to locate workspace root with [workspace] Cargo.toml"); // Collect Rust files under crates/**/src/**/*.rs and any build.rs under crates/** let files = collect_rust_files(&workspace_root); @@ -70,14 +68,21 @@ fn test_no_legacy_artifacts() { for (tok, line, col) in toks { if is_forbidden_ident(&tok) { - violations.insert(format!("{}:{}:{} identifier '{}': legacy token", - rel(&workspace_root, path), line, col, tok)); + violations.insert(format!( + "{}:{}:{} identifier '{}': legacy token", + rel(&workspace_root, path), + line, + col, + tok + )); } } } if !violations.is_empty() { - let mut msg = String::from("Legacy artifacts detected (RC/HIP/scope helpers). Please remove or rename.\n"); + let mut msg = String::from( + "Legacy artifacts detected (RC/HIP/scope helpers). Please remove or rename.\n", + ); for v in &violations { msg.push_str(" - "); msg.push_str(v); @@ -110,15 +115,18 @@ fn find_workspace_root() -> Option { fn collect_rust_files(root: &Path) -> Vec { let mut out = Vec::new(); let crates_dir = root.join("crates"); - if !crates_dir.exists() { return out; } + if !crates_dir.exists() { + return out; + } let mut stack = vec![crates_dir]; while let Some(dir) = stack.pop() { // Exclude noisy/non-code directories early let name_lc = dir.file_name().and_then(|s| s.to_str()).unwrap_or("").to_ascii_lowercase(); - if matches!(name_lc.as_str(), - "target" | "docs" | "files" | "sdcard" | "test-cartridges" | "temp") - || name_lc.starts_with("dist") + if matches!( + name_lc.as_str(), + "target" | "docs" | "files" | "sdcard" | "test-cartridges" | "temp" + ) || name_lc.starts_with("dist") { continue; } @@ -143,10 +151,8 @@ fn collect_rust_files(root: &Path) -> Vec { } fn path_segment_violation(path: &Path) -> Option { - let mut segs: Vec = path - .components() - .filter_map(|c| c.as_os_str().to_str().map(|s| s.to_string())) - .collect(); + let mut segs: Vec = + path.components().filter_map(|c| c.as_os_str().to_str().map(|s| s.to_string())).collect(); if let Some(fname) = path.file_stem().and_then(|s| s.to_str()) { segs.push(fname.to_string()); } @@ -154,10 +160,7 @@ fn path_segment_violation(path: &Path) -> Option { let seg_lc = seg.to_ascii_lowercase(); for &bad in FORBIDDEN_PATH_SEGMENTS { if seg_lc == bad.to_ascii_lowercase() { - return Some(format!( - "{} path-segment '{}'", - path.display(), seg - )); + return Some(format!("{} path-segment '{}'", path.display(), seg)); } } } @@ -188,15 +191,22 @@ fn tokenize_identifiers(text: &str) -> Vec<(String, usize, usize)> { let start_line = line; let start_col = col; let start = i; - i += 1; col += 1; + i += 1; + col += 1; while i < bytes.len() { let c = bytes[i] as char; - if is_ident_part(c) { i += 1; col += 1; } else { break; } + if is_ident_part(c) { + i += 1; + col += 1; + } else { + break; + } } let tok = &text[start..i]; out.push((tok.to_string(), start_line, start_col)); } else { - i += 1; col += 1; + i += 1; + col += 1; } } out @@ -221,14 +231,20 @@ fn strip_comments_and_strings(src: &str) -> String { while i < b.len() { let c = b[i] as char; // Preserve newlines to maintain line numbers - if c == '\n' { out.push('\n'); i += 1; continue; } + if c == '\n' { + out.push('\n'); + i += 1; + continue; + } // Try to match line comment if c == '/' && i + 1 < b.len() && b[i + 1] as char == '/' { i += 2; while i < b.len() { let ch = b[i] as char; - if ch == '\n' { break; } + if ch == '\n' { + break; + } i += 1; } continue; // newline is handled at top on next loop @@ -239,8 +255,13 @@ fn strip_comments_and_strings(src: &str) -> String { i += 2; while i + 1 < b.len() { let ch = b[i] as char; - if ch == '\n' { out.push('\n'); } - if ch == '*' && b[i + 1] as char == '/' { i += 2; break; } + if ch == '\n' { + out.push('\n'); + } + if ch == '*' && b[i + 1] as char == '/' { + i += 2; + break; + } i += 1; } continue; @@ -251,7 +272,10 @@ fn strip_comments_and_strings(src: &str) -> String { let mut j = i + 1; let mut hashes = 0usize; if j < b.len() && b[j] as char == '#' { - while j < b.len() && b[j] as char == '#' && hashes < 10 { hashes += 1; j += 1; } + while j < b.len() && b[j] as char == '#' && hashes < 10 { + hashes += 1; + j += 1; + } } if j < b.len() && b[j] as char == '"' { // Found start of raw string @@ -259,13 +283,18 @@ fn strip_comments_and_strings(src: &str) -> String { let mut end_found = false; while j < b.len() { let ch = b[j] as char; - if ch == '\n' { out.push('\n'); j += 1; continue; } + if ch == '\n' { + out.push('\n'); + j += 1; + continue; + } if ch == '"' { // check for closing hashes let mut k = j + 1; let mut matched = 0usize; while matched < hashes && k < b.len() && b[k] as char == '#' { - matched += 1; k += 1; + matched += 1; + k += 1; } if matched == hashes { i = k; // consume entire raw string @@ -278,7 +307,9 @@ fn strip_comments_and_strings(src: &str) -> String { } j += 1; } - if !end_found { i = j; } // EOF inside string + if !end_found { + i = j; + } // EOF inside string continue; } } @@ -288,12 +319,17 @@ fn strip_comments_and_strings(src: &str) -> String { i += 1; // skip starting quote while i < b.len() { let ch = b[i] as char; - if ch == '\n' { out.push('\n'); } + if ch == '\n' { + out.push('\n'); + } if ch == '\\' { i += 2; // skip escaped char continue; } - if ch == '"' { i += 1; break; } + if ch == '"' { + i += 1; + break; + } i += 1; } continue; @@ -320,22 +356,38 @@ mod pathdiff { // pop common prefix let mut comps_a: Vec = Vec::new(); let mut comps_b: Vec = Vec::new(); - for c in ita { comps_a.push(c); } - for c in itb { comps_b.push(c); } + for c in ita { + comps_a.push(c); + } + for c in itb { + comps_b.push(c); + } let mut i = 0usize; - while i < comps_a.len() && i < comps_b.len() && comps_a[i] == comps_b[i] { i += 1; } + while i < comps_a.len() && i < comps_b.len() && comps_a[i] == comps_b[i] { + i += 1; + } let mut result = PathBuf::new(); - for _ in i..comps_a.len() { result.push(".."); } - for c in &comps_b[i..] { result.push(c.as_os_str()); } + for _ in i..comps_a.len() { + result.push(".."); + } + for c in &comps_b[i..] { + result.push(c.as_os_str()); + } Some(result) } - trait Absolutize { fn absolutize(&self) -> PathBuf; } + trait Absolutize { + fn absolutize(&self) -> PathBuf; + } impl Absolutize for Path { fn absolutize(&self) -> PathBuf { - if self.is_absolute() { self.to_path_buf() } else { std::env::current_dir().unwrap().join(self) } + if self.is_absolute() { + self.to_path_buf() + } else { + std::env::current_dir().unwrap().join(self) + } } } } diff --git a/crates/tools/pbxgen-stress/src/lib.rs b/crates/tools/pbxgen-stress/src/lib.rs index cb0c32b7..632a2d97 100644 --- a/crates/tools/pbxgen-stress/src/lib.rs +++ b/crates/tools/pbxgen-stress/src/lib.rs @@ -1,10 +1,14 @@ use anyhow::Result; use prometeu_bytecode::assembler::assemble; -use prometeu_bytecode::model::{BytecodeModule, ConstantPoolEntry, DebugInfo, Export, FunctionMeta}; +use prometeu_bytecode::model::{ + BytecodeModule, ConstantPoolEntry, DebugInfo, Export, FunctionMeta, +}; use std::fs; use std::path::PathBuf; -fn asm(s: &str) -> Vec { assemble(s).expect("assemble") } +fn asm(s: &str) -> Vec { + assemble(s).expect("assemble") +} pub fn generate() -> Result<()> { let mut rom: Vec = Vec::new(); @@ -12,16 +16,14 @@ pub fn generate() -> Result<()> { heavy_load(&mut rom); // light_load(&mut rom); - let functions = vec![ - FunctionMeta { - code_offset: 0, - code_len: rom.len() as u32, - param_slots: 0, - local_slots: 2, - return_slots: 0, - max_stack_slots: 16, - }, - ]; + let functions = vec![FunctionMeta { + code_offset: 0, + code_len: rom.len() as u32, + param_slots: 0, + local_slots: 2, + return_slots: 0, + max_stack_slots: 16, + }]; let module = BytecodeModule { version: 0, @@ -72,7 +74,7 @@ fn heavy_load(mut rom: &mut Vec) { // draw 100 discs using t for animation // draw 20 texts using t for animation // RET (runtime handles the frame loop) - + // --- init locals --- // local 0: scratch // local 1: loop counter for discs @@ -119,7 +121,9 @@ fn heavy_load(mut rom: &mut Vec) { rom.extend(asm("JMP_IF_FALSE 0")); // x = (t * 3 + i * 40) % 320 - rom.extend(asm("GET_GLOBAL 0\nPUSH_I32 3\nMUL\nGET_LOCAL 1\nPUSH_I32 40\nMUL\nADD\nPUSH_I32 320\nMOD")); + rom.extend(asm( + "GET_GLOBAL 0\nPUSH_I32 3\nMUL\nGET_LOCAL 1\nPUSH_I32 40\nMUL\nADD\nPUSH_I32 320\nMOD", + )); // y = (i * 30 + t) % 180 rom.extend(asm("GET_LOCAL 1\nPUSH_I32 30\nMUL\nGET_GLOBAL 0\nADD\nPUSH_I32 180\nMOD")); // string (toggle between "stress" and "frame") diff --git a/crates/tools/pbxgen-stress/src/main.rs b/crates/tools/pbxgen-stress/src/main.rs index 5ad26402..f956b927 100644 --- a/crates/tools/pbxgen-stress/src/main.rs +++ b/crates/tools/pbxgen-stress/src/main.rs @@ -1,3 +1,5 @@ use anyhow::Result; -fn main() -> Result<()> { pbxgen_stress::generate() } +fn main() -> Result<()> { + pbxgen_stress::generate() +} diff --git a/docs/pull-requests/PR-2 - PBX SYSC and HOSTCALL Loader Patching.md b/docs/pull-requests/PR-2 - PBX SYSC and HOSTCALL Loader Patching.md new file mode 100644 index 00000000..f3a17166 --- /dev/null +++ b/docs/pull-requests/PR-2 - PBX SYSC and HOSTCALL Loader Patching.md @@ -0,0 +1,324 @@ +# PR-2 - PBX SYSC and HOSTCALL Loader Patching + +## Goal + +Teach the runtime to load canonical host bindings from PBX, resolve them at load time, validate ABI and capabilities, and rewrite pre-load host calls into final numeric syscalls. + +This PR assumes PR-1 is already available, so cartridge capabilities are exposed to the loader as internal `CapFlags`. + +## Why + +Prometeu's hardware contract is: + +- source- and SDK-level host APIs map to canonical identities `(module, name, version)` +- load-time resolution maps those identities to numeric syscall ids +- runtime execution is numeric-only via `SYSCALL ` + +The runtime already has the important building blocks: + +- canonical syscall registry +- load-time `resolve_program_syscalls` +- verifier support for numeric `SYSCALL ` +- VM dispatch by numeric id + +What is missing is the PBX and loader wiring. + +## Scope + +In scope: + +- add a mandatory PBX `SYSC` section +- extend each `SYSC` entry with ABI shape +- add pre-load opcode `HOSTCALL ` +- parse `SYSC` at load time +- resolve `SYSC` against the host syscall registry +- validate declared ABI against authoritative host metadata +- validate capabilities using cartridge-granted `CapFlags` +- reject unused `SYSC` entries +- reject out-of-bounds `HOSTCALL` indices +- patch all `HOSTCALL ` to `SYSCALL ` +- ensure verifier runs only on the patched image + +Out of scope: + +- compiler emission +- stdlib SDK pack format +- final external tooling for PBX generation +- platform policy beyond current manifest-derived granted capabilities + +## Architectural Contract + +### PBX `SYSC` + +`SYSC` is a unique, deduplicated, program-wide table of declared host bindings. + +Each entry carries: + +- `module` +- `name` +- `version` +- `arg_slots` +- `ret_slots` + +`SYSC` is mandatory for every valid PBX. +If the program requires no host bindings, `SYSC` is present with `count = 0`. + +### Pre-load callsites + +The compiler-side artifact form is: + +```text +HOSTCALL +``` + +Rules: + +- `sysc_index` is zero-based into the `SYSC` table +- code must not contain final `SYSCALL ` for unresolved host-backed SDK calls +- the final executable image given to the VM must contain no `HOSTCALL` + +### Load-time responsibilities + +The loader must: + +1. parse `SYSC` +2. resolve each entry to `syscall_id` +3. validate `arg_slots` and `ret_slots` +4. validate capabilities +5. scan code for `HOSTCALL` +6. mark used `SYSC` indices +7. reject out-of-bounds indices +8. reject unused `SYSC` entries +9. rewrite `HOSTCALL ` into `SYSCALL ` +10. ensure no `HOSTCALL` remains +11. only then hand the image to the verifier + +## PBX Format Changes + +### `prometeu-bytecode` + +Add a new section kind: + +- `SYSC` + +Recommended temporary binary layout: + +```text +u32 count +repeat count times: + u16 module_len + [module_len bytes UTF-8] + u16 name_len + [name_len bytes UTF-8] + u16 version + u16 arg_slots + u16 ret_slots +``` + +Rules: + +- strings are UTF-8 +- duplicate canonical identities are invalid +- malformed lengths are invalid +- missing `SYSC` is invalid + +### Bytecode model changes + +Extend `BytecodeModule` with a syscall declaration vector. + +Suggested shape: + +```rust +pub struct SyscallDecl { + pub module: String, + pub name: String, + pub version: u16, + pub arg_slots: u16, + pub ret_slots: u16, +} +``` + +Then: + +```rust +pub syscalls: Vec +``` + +Serialization/deserialization must include section kind for `SYSC`. + +## Opcode Changes + +### Add `HOSTCALL` + +Add a new opcode: + +- `HOSTCALL` + +Immediate: + +- `u32 sysc_index` + +Recommended behavior: + +- valid only in pre-load artifact form +- not allowed to reach final verifier/VM execution path + +Required code updates: + +- opcode enum +- decoder +- opcode spec +- assembler +- disassembler + +Verifier recommendation: + +- the verifier does not need to support `HOSTCALL` +- it should continue to run after loader patching only + +## Loader Integration + +### Resolution + +Use the existing canonical resolver in `prometeu-hal::syscalls`. + +For each `SYSC` entry: + +1. resolve `(module, name, version)` +2. obtain `SyscallResolved` +3. compare `arg_slots` +4. compare `ret_slots` +5. compare required capability against granted cartridge flags + +If any check fails, load fails deterministically. + +### Capability source + +Capabilities come from the already-loaded cartridge manifest via PR-1. + +This PR must not invent authority from PBX contents. + +### Code patching + +Recommended algorithm: + +1. parse code buffer +2. whenever `HOSTCALL ` is found: +3. validate `index < syscalls.len()` +4. mark that `SYSC[index]` is used +5. rewrite opcode/immediate in place or rebuild the code buffer +6. emit final `SYSCALL ` + +After scan: + +1. every `SYSC` entry must be marked used +2. no `HOSTCALL` may remain + +Either patching strategy is acceptable: + +- mutate byte buffer in place +- rebuild a fresh patched buffer + +The final `ProgramImage` must contain only numeric `SYSCALL `. + +## Verifier Contract + +No verifier redesign is required. + +The intended contract is: + +- loader validates interface compatibility +- verifier validates final numeric program structure + +This means: + +- loader checks `SYSC`-declared ABI against host metadata +- verifier checks stack effects of final `SYSCALL ` using existing runtime metadata + +This is intentional and not considered harmful duplication. + +## Proposed Code Areas + +### `prometeu-bytecode` + +Likely files: + +- `src/model.rs` +- `src/opcode.rs` +- `src/opcode_spec.rs` +- `src/decoder.rs` +- `src/assembler.rs` +- `src/disassembler.rs` +- `src/lib.rs` + +### `prometeu-hal` + +Likely files: + +- `src/cartridge_loader.rs` +- possibly helper types or resolver glue near syscall loading logic + +### `prometeu-vm` + +Likely files: + +- load path in `src/virtual_machine.rs` + +Required behavior: + +- patch before `Verifier::verify(...)` + +## Deterministic Load Errors + +Load must fail for at least: + +1. missing `SYSC` +2. malformed `SYSC` +3. invalid UTF-8 +4. duplicate syscall identities +5. unknown syscall identity +6. ABI mismatch between `SYSC` and host metadata +7. missing capability +8. `HOSTCALL` with out-of-bounds `sysc_index` +9. declared `SYSC` entry unused by all `HOSTCALL`s +10. `HOSTCALL` still present after patch + +## Acceptance Criteria + +- PBX parser supports mandatory `SYSC` +- `BytecodeModule` carries syscall declarations +- runtime understands `HOSTCALL` +- loader resolves `SYSC` entries before verification +- loader validates `arg_slots` and `ret_slots` +- loader validates capabilities against cartridge flags +- loader rewrites `HOSTCALL` to `SYSCALL` +- verifier runs only on patched code +- final VM execution path remains numeric-only + +## Tests + +Add tests covering: + +1. valid PBX with empty `SYSC` and no `HOSTCALL` +2. valid PBX with one syscall and one `HOSTCALL` +3. unknown syscall identity +4. capability mismatch +5. ABI mismatch +6. missing `SYSC` +7. duplicate `SYSC` entries +8. malformed `SYSC` payload +9. `HOSTCALL` index out of bounds +10. unused `SYSC` entry +11. patched image contains only `SYSCALL` + +Prefer synthetic in-memory PBX images in tests. + +## Definition of Done + +After this PR: + +- PBX declares canonical host bindings in `SYSC` +- pre-load code references those bindings with `HOSTCALL` +- the loader resolves and validates them during load +- the loader patches executable code to `SYSCALL ` +- the verifier and VM continue to operate on numeric syscalls only