From c7786fa8b0b2b197e6182b0971313a3bd8070ded Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Wed, 18 Feb 2026 07:31:29 +0000 Subject: [PATCH] quality baseline --- .github/workflows/ci.yml | 29 + Cargo.lock | 68 +- Cargo.toml | 1 + Makefile | 15 + crates/console/prometeu-bytecode/src/abi.rs | 2 +- .../console/prometeu-bytecode/src/decoder.rs | 9 +- .../console/prometeu-bytecode/src/layout.rs | 20 +- crates/console/prometeu-bytecode/src/lib.rs | 34 +- crates/console/prometeu-bytecode/src/model.rs | 234 +++--- .../console/prometeu-bytecode/src/opcode.rs | 10 +- .../prometeu-bytecode/src/opcode_spec.rs | 590 ++++++++++++-- .../prometeu-bytecode/src/program_image.rs | 93 +-- crates/console/prometeu-bytecode/src/value.rs | 8 +- crates/console/prometeu-drivers/src/asset.rs | 299 ++++--- crates/console/prometeu-drivers/src/audio.rs | 140 ++-- crates/console/prometeu-drivers/src/gfx.rs | 363 +++++++-- .../console/prometeu-drivers/src/hardware.rs | 61 +- crates/console/prometeu-drivers/src/lib.rs | 8 +- .../prometeu-drivers/src/memory_banks.rs | 4 +- crates/console/prometeu-drivers/src/pad.rs | 72 +- crates/console/prometeu-drivers/src/touch.rs | 20 +- .../prometeu-drivers/tests/heartbeat.rs | 20 +- .../src/firmware/firmware.rs | 41 +- .../src/firmware/firmware_state.rs | 2 +- .../firmware/firmware_step_crash_screen.rs | 8 +- .../firmware/firmware_step_game_running.rs | 4 +- .../src/firmware/firmware_step_hub_home.rs | 8 +- .../src/firmware/firmware_step_launch_hub.rs | 11 +- .../firmware/firmware_step_load_cartridge.rs | 19 +- .../src/firmware/firmware_step_reset.rs | 4 +- .../firmware/firmware_step_splash_screen.rs | 10 +- .../prometeu-firmware/src/firmware/mod.rs | 12 +- .../src/firmware/prometeu_context.rs | 4 +- crates/console/prometeu-hal/src/asset.rs | 10 +- .../console/prometeu-hal/src/asset_bridge.rs | 4 +- .../console/prometeu-hal/src/audio_bridge.rs | 2 +- .../prometeu-hal/src/cartridge_loader.rs | 8 +- crates/console/prometeu-hal/src/color.rs | 9 +- .../prometeu-hal/src/debugger_protocol.rs | 39 +- crates/console/prometeu-hal/src/gfx_bridge.rs | 10 +- .../console/prometeu-hal/src/host_return.rs | 4 +- crates/console/prometeu-hal/src/lib.rs | 32 +- .../prometeu-hal/src/log/log_service.rs | 20 +- crates/console/prometeu-hal/src/log/mod.rs | 4 +- .../prometeu-hal/src/native_helpers.rs | 2 +- .../prometeu-hal/src/native_interface.rs | 10 +- crates/console/prometeu-hal/src/sample.rs | 7 +- crates/console/prometeu-hal/src/sound_bank.rs | 6 +- crates/console/prometeu-hal/src/syscalls.rs | 10 +- crates/console/prometeu-hal/src/telemetry.rs | 28 +- crates/console/prometeu-hal/src/tile_bank.rs | 8 +- crates/console/prometeu-hal/src/tile_layer.rs | 23 +- .../console/prometeu-hal/src/touch_bridge.rs | 2 +- crates/console/prometeu-hal/src/vm_fault.rs | 2 +- crates/console/prometeu-system/src/lib.rs | 4 +- .../prometeu-system/src/programs/mod.rs | 2 +- .../src/programs/prometeu_hub/prometeu_hub.rs | 37 +- .../programs/prometeu_hub/window_manager.rs | 27 +- .../src/services/fs/fs_backend.rs | 4 +- .../prometeu-system/src/services/fs/mod.rs | 4 +- .../src/services/fs/virtual_fs.rs | 41 +- .../prometeu-system/src/services/mod.rs | 2 +- .../src/virtual_machine_runtime.rs | 202 +++-- crates/console/prometeu-vm/Cargo.toml | 3 + crates/console/prometeu-vm/src/call_frame.rs | 2 +- crates/console/prometeu-vm/src/lib.rs | 8 +- .../prometeu-vm/src/local_addressing.rs | 9 +- crates/console/prometeu-vm/src/scope_frame.rs | 2 +- crates/console/prometeu-vm/src/verifier.rs | 200 +++-- .../prometeu-vm/src/virtual_machine.rs | 729 ++++++++++++------ crates/console/prometeu-vm/tests/smoke.rs | 29 + crates/dev/prometeu-test-support/Cargo.toml | 8 + crates/dev/prometeu-test-support/src/lib.rs | 108 +++ .../prometeu-host-desktop-winit/src/audio.rs | 38 +- .../prometeu-host-desktop-winit/src/cap.rs | 13 +- .../src/debugger.rs | 88 ++- .../src/fs_backend.rs | 14 +- .../prometeu-host-desktop-winit/src/input.rs | 12 +- .../prometeu-host-desktop-winit/src/lib.rs | 24 +- .../src/log_sink.rs | 2 +- .../prometeu-host-desktop-winit/src/main.rs | 2 +- .../prometeu-host-desktop-winit/src/runner.rs | 180 +++-- .../prometeu-host-desktop-winit/src/stats.rs | 22 +- .../prometeu-cli/src/bin/prometeu-runtime.rs | 4 +- crates/tools/prometeu-cli/src/main.rs | 16 +- docs/ARCHITECTURE.md | 25 + docs/README.md | 15 - docs/STYLE.md | 49 ++ docs/phase-03-frontend-api.md | 19 - rustfmt.toml | 3 + 90 files changed, 3076 insertions(+), 1334 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 Makefile create mode 100644 crates/console/prometeu-vm/tests/smoke.rs create mode 100644 crates/dev/prometeu-test-support/Cargo.toml create mode 100644 crates/dev/prometeu-test-support/src/lib.rs create mode 100644 docs/ARCHITECTURE.md delete mode 100644 docs/README.md create mode 100644 docs/STYLE.md delete mode 100644 docs/phase-03-frontend-api.md create mode 100644 rustfmt.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..31d5ff08 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,29 @@ +name: CI + +on: + push: + branches: [ main, master ] + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + + - uses: Swatinem/rust-cache@v2 + + - name: Format check + run: cargo fmt -- --check + + - name: Clippy + run: cargo clippy --workspace --all-features + + - name: Test + run: cargo test --workspace --all-targets --all-features --no-fail-fast diff --git a/Cargo.lock b/Cargo.lock index 71c69272..bdcafc99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,7 +25,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom", + "getrandom 0.3.4", "once_cell", "version_check", "zerocopy", @@ -669,6 +669,17 @@ dependencies = [ "windows-link", ] +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "getrandom" version = "0.3.4" @@ -879,7 +890,7 @@ version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "getrandom", + "getrandom 0.3.4", "libc", ] @@ -1545,6 +1556,15 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "presser" version = "0.3.1" @@ -1644,12 +1664,20 @@ dependencies = [ "serde_json", ] +[[package]] +name = "prometeu-test-support" +version = "0.1.0" +dependencies = [ + "rand", +] + [[package]] name = "prometeu-vm" version = "0.1.0" dependencies = [ "prometeu-bytecode", "prometeu-hal", + "prometeu-test-support", ] [[package]] @@ -1676,6 +1704,36 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + [[package]] name = "range-alloc" version = "0.1.4" @@ -2162,6 +2220,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" diff --git a/Cargo.toml b/Cargo.toml index d85cf247..a791ec4b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ "crates/host/prometeu-host-desktop-winit", "crates/tools/prometeu-cli", + "crates/dev/prometeu-test-support", ] resolver = "2" diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..71c4ef13 --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +.PHONY: fmt fmt-check clippy test ci + +fmt: + cargo fmt + +fmt-check: + cargo fmt -- --check + +clippy: + cargo clippy --workspace --all-features + +test: + cargo test --workspace --all-targets --all-features --no-fail-fast + +ci: fmt-check clippy test diff --git a/crates/console/prometeu-bytecode/src/abi.rs b/crates/console/prometeu-bytecode/src/abi.rs index 2b4c9b10..942dbe9b 100644 --- a/crates/console/prometeu-bytecode/src/abi.rs +++ b/crates/console/prometeu-bytecode/src/abi.rs @@ -47,4 +47,4 @@ pub struct TrapInfo { pub pc: u32, /// Optional source span information if debug symbols are available. pub span: Option, -} \ No newline at end of file +} diff --git a/crates/console/prometeu-bytecode/src/decoder.rs b/crates/console/prometeu-bytecode/src/decoder.rs index f787c3cd..4d8ee6dd 100644 --- a/crates/console/prometeu-bytecode/src/decoder.rs +++ b/crates/console/prometeu-bytecode/src/decoder.rs @@ -112,10 +112,5 @@ pub fn decode_next(pc: usize, bytes: &'_ [u8]) -> Result, Decod }); } - Ok(DecodedInstr { - opcode, - pc, - next_pc: imm_end, - imm: &bytes[imm_start..imm_end], - }) -} \ No newline at end of file + Ok(DecodedInstr { opcode, pc, next_pc: imm_end, imm: &bytes[imm_start..imm_end] }) +} diff --git a/crates/console/prometeu-bytecode/src/layout.rs b/crates/console/prometeu-bytecode/src/layout.rs index cf2f94dc..94647937 100644 --- a/crates/console/prometeu-bytecode/src/layout.rs +++ b/crates/console/prometeu-bytecode/src/layout.rs @@ -18,7 +18,10 @@ pub struct FunctionLayout { /// then using the next function's start as the current end; the last /// function ends at `code_len_total`. /// - The returned vector is indexed by the original function indices. -pub fn compute_function_layouts(functions: &[FunctionMeta], code_len_total: usize) -> Vec { +pub fn compute_function_layouts( + functions: &[FunctionMeta], + code_len_total: usize, +) -> Vec { // Build index array and sort by start offset (stable to preserve relative order). let mut idxs: Vec = (0..functions.len()).collect(); idxs.sort_by_key(|&i| functions[i].code_offset as usize); @@ -28,7 +31,14 @@ pub fn compute_function_layouts(functions: &[FunctionMeta], code_len_total: usiz if let [a, b] = *w { let sa = functions[a].code_offset as usize; let sb = functions[b].code_offset as usize; - debug_assert!(sa < sb, "Function code_offset must be strictly increasing: {} vs {} (indices {} and {})", sa, sb, a, b); + debug_assert!( + sa < sb, + "Function code_offset must be strictly increasing: {} vs {} (indices {} and {})", + sa, + sb, + a, + b + ); } } @@ -78,7 +88,11 @@ mod tests { for i in 0..3 { let l = &layouts[i]; - assert_eq!(l.end - l.start, (funcs.get(i + 1).map(|n| n.code_offset as usize).unwrap_or(40)) - (funcs[i].code_offset as usize)); + assert_eq!( + l.end - l.start, + (funcs.get(i + 1).map(|n| n.code_offset as usize).unwrap_or(40)) + - (funcs[i].code_offset as usize) + ); } } } diff --git a/crates/console/prometeu-bytecode/src/lib.rs b/crates/console/prometeu-bytecode/src/lib.rs index 2e93638d..ba82e094 100644 --- a/crates/console/prometeu-bytecode/src/lib.rs +++ b/crates/console/prometeu-bytecode/src/lib.rs @@ -1,33 +1,21 @@ +mod abi; +mod decoder; +mod layout; +mod model; mod opcode; mod opcode_spec; -mod abi; -mod layout; -mod decoder; -mod model; -mod value; mod program_image; +mod value; pub use abi::{ - TrapInfo, - TRAP_INVALID_LOCAL, - TRAP_OOB, + TrapInfo, TRAP_BAD_RET_SLOTS, TRAP_DEAD_GATE, TRAP_DIV_ZERO, TRAP_INVALID_FUNC, + TRAP_INVALID_GATE, TRAP_INVALID_LOCAL, TRAP_INVALID_SYSCALL, TRAP_OOB, TRAP_STACK_UNDERFLOW, TRAP_TYPE, - TRAP_BAD_RET_SLOTS, - TRAP_DEAD_GATE, - TRAP_DIV_ZERO, - TRAP_INVALID_FUNC, - TRAP_INVALID_GATE, - TRAP_STACK_UNDERFLOW, - TRAP_INVALID_SYSCALL, }; -pub use model::{ - BytecodeLoader, - FunctionMeta, - LoadError, -}; -pub use value::Value; -pub use opcode_spec::OpCodeSpecExt; -pub use opcode::OpCode; pub use decoder::{decode_next, DecodeError}; pub use layout::{compute_function_layouts, FunctionLayout}; +pub use model::{BytecodeLoader, FunctionMeta, LoadError}; +pub use opcode::OpCode; +pub use opcode_spec::OpCodeSpecExt; pub use program_image::ProgramImage; +pub use value::Value; diff --git a/crates/console/prometeu-bytecode/src/model.rs b/crates/console/prometeu-bytecode/src/model.rs index 0034adbd..fa17cbd2 100644 --- a/crates/console/prometeu-bytecode/src/model.rs +++ b/crates/console/prometeu-bytecode/src/model.rs @@ -3,7 +3,7 @@ use crate::opcode::OpCode; use serde::{Deserialize, Serialize}; /// An entry in the Constant Pool. -/// +/// /// The Constant Pool is a table of unique values used by the program. /// Instead of embedding large data (like strings) directly in the instruction stream, /// the bytecode uses `PushConst ` to load these values onto the stack. @@ -62,9 +62,9 @@ pub struct Export { /// Represents the final serialized format of a PBS v0 module. /// -/// This structure is a pure data container for the PBS format. It does NOT -/// contain any linker-like logic (symbol resolution, patching, etc.). -/// All multi-module programs must be flattened and linked by the compiler +/// This structure is a pure data container for the PBS format. It does NOT +/// contain any linker-like logic (symbol resolution, patching, etc.). +/// All multi-module programs must be flattened and linked by the compiler /// before being serialized into this format. #[derive(Debug, Clone, PartialEq)] pub struct BytecodeModule { @@ -81,15 +81,26 @@ impl BytecodeModule { let cp_data = self.serialize_const_pool(); let func_data = self.serialize_functions(); let code_data = self.code.clone(); - let debug_data = self.debug_info.as_ref().map(|di| self.serialize_debug(di)).unwrap_or_default(); + let debug_data = + self.debug_info.as_ref().map(|di| self.serialize_debug(di)).unwrap_or_default(); let export_data = self.serialize_exports(); let mut final_sections = Vec::new(); - if !cp_data.is_empty() { final_sections.push((0, cp_data)); } - if !func_data.is_empty() { final_sections.push((1, func_data)); } - if !code_data.is_empty() { final_sections.push((2, code_data)); } - if !debug_data.is_empty() { final_sections.push((3, debug_data)); } - if !export_data.is_empty() { final_sections.push((4, export_data)); } + if !cp_data.is_empty() { + final_sections.push((0, cp_data)); + } + if !func_data.is_empty() { + final_sections.push((1, func_data)); + } + if !code_data.is_empty() { + final_sections.push((2, code_data)); + } + if !debug_data.is_empty() { + final_sections.push((3, debug_data)); + } + if !export_data.is_empty() { + final_sections.push((4, export_data)); + } let mut out = Vec::new(); // Magic "PBS\0" @@ -104,7 +115,7 @@ impl BytecodeModule { out.extend_from_slice(&[0u8; 20]); let mut current_offset = 32 + (final_sections.len() as u32 * 12); - + // Write section table for (kind, data) in &final_sections { let k: u32 = *kind; @@ -123,7 +134,9 @@ impl BytecodeModule { } fn serialize_const_pool(&self) -> Vec { - if self.const_pool.is_empty() { return Vec::new(); } + if self.const_pool.is_empty() { + return Vec::new(); + } let mut data = Vec::new(); data.extend_from_slice(&(self.const_pool.len() as u32).to_le_bytes()); for entry in &self.const_pool { @@ -157,7 +170,9 @@ impl BytecodeModule { } fn serialize_functions(&self) -> Vec { - if self.functions.is_empty() { return Vec::new(); } + if self.functions.is_empty() { + return Vec::new(); + } let mut data = Vec::new(); data.extend_from_slice(&(self.functions.len() as u32).to_le_bytes()); for f in &self.functions { @@ -191,7 +206,9 @@ impl BytecodeModule { } fn serialize_exports(&self) -> Vec { - if self.exports.is_empty() { return Vec::new(); } + if self.exports.is_empty() { + return Vec::new(); + } let mut data = Vec::new(); data.extend_from_slice(&(self.exports.len() as u32).to_le_bytes()); for exp in &self.exports { @@ -202,7 +219,6 @@ impl BytecodeModule { } data } - } pub struct BytecodeLoader; @@ -224,22 +240,34 @@ impl BytecodeLoader { } let endianness = bytes[6]; - if endianness != 0 { // 0 = Little Endian + if endianness != 0 { + // 0 = Little Endian return Err(LoadError::InvalidEndianness); } let section_count = u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]); - + let mut sections = Vec::new(); let mut pos = 32; for _ in 0..section_count { if pos + 12 > bytes.len() { return Err(LoadError::UnexpectedEof); } - let kind = u32::from_le_bytes([bytes[pos], bytes[pos+1], bytes[pos+2], bytes[pos+3]]); - let offset = u32::from_le_bytes([bytes[pos+4], bytes[pos+5], bytes[pos+6], bytes[pos+7]]); - let length = u32::from_le_bytes([bytes[pos+8], bytes[pos+9], bytes[pos+10], bytes[pos+11]]); - + let kind = + u32::from_le_bytes([bytes[pos], bytes[pos + 1], bytes[pos + 2], bytes[pos + 3]]); + let offset = u32::from_le_bytes([ + bytes[pos + 4], + bytes[pos + 5], + bytes[pos + 6], + bytes[pos + 7], + ]); + let length = u32::from_le_bytes([ + bytes[pos + 8], + bytes[pos + 9], + bytes[pos + 10], + bytes[pos + 11], + ]); + // Basic bounds check if (offset as usize) + (length as usize) > bytes.len() { return Err(LoadError::SectionOutOfBounds); @@ -254,7 +282,7 @@ impl BytecodeLoader { for j in i + 1..sections.len() { let (_, o1, l1) = sections[i]; let (_, o2, l2) = sections[j]; - + if (o1 < o2 + l2) && (o2 < o1 + l1) { return Err(LoadError::OverlappingSections); } @@ -273,19 +301,24 @@ impl BytecodeLoader { for (kind, offset, length) in sections { let section_data = &bytes[offset as usize..(offset + length) as usize]; match kind { - 0 => { // Const Pool + 0 => { + // Const Pool module.const_pool = parse_const_pool(section_data)?; } - 1 => { // Functions + 1 => { + // Functions module.functions = parse_functions(section_data)?; } - 2 => { // Code + 2 => { + // Code module.code = section_data.to_vec(); } - 3 => { // Debug Info + 3 => { + // Debug Info module.debug_info = Some(parse_debug_section(section_data)?); } - 4 => { // Exports + 4 => { + // Exports module.exports = parse_exports(section_data)?; } _ => {} // Skip unknown or optional sections @@ -309,7 +342,7 @@ fn parse_const_pool(data: &[u8]) -> Result, LoadError> { let count = u32::from_le_bytes([data[0], data[1], data[2], data[3]]) as usize; let mut cp = Vec::with_capacity(count); let mut pos = 4; - + for _ in 0..count { if pos >= data.len() { return Err(LoadError::UnexpectedEof); @@ -318,35 +351,52 @@ fn parse_const_pool(data: &[u8]) -> Result, LoadError> { pos += 1; match tag { 0 => cp.push(ConstantPoolEntry::Null), - 1 => { // Int64 - if pos + 8 > data.len() { return Err(LoadError::UnexpectedEof); } - let val = i64::from_le_bytes(data[pos..pos+8].try_into().unwrap()); + 1 => { + // Int64 + if pos + 8 > data.len() { + return Err(LoadError::UnexpectedEof); + } + let val = i64::from_le_bytes(data[pos..pos + 8].try_into().unwrap()); cp.push(ConstantPoolEntry::Int64(val)); pos += 8; } - 2 => { // Float64 - if pos + 8 > data.len() { return Err(LoadError::UnexpectedEof); } - let val = f64::from_le_bytes(data[pos..pos+8].try_into().unwrap()); + 2 => { + // Float64 + if pos + 8 > data.len() { + return Err(LoadError::UnexpectedEof); + } + let val = f64::from_le_bytes(data[pos..pos + 8].try_into().unwrap()); cp.push(ConstantPoolEntry::Float64(val)); pos += 8; } - 3 => { // Boolean - if pos >= data.len() { return Err(LoadError::UnexpectedEof); } + 3 => { + // Boolean + if pos >= data.len() { + return Err(LoadError::UnexpectedEof); + } cp.push(ConstantPoolEntry::Boolean(data[pos] != 0)); pos += 1; } - 4 => { // String - if pos + 4 > data.len() { return Err(LoadError::UnexpectedEof); } - let len = u32::from_le_bytes(data[pos..pos+4].try_into().unwrap()) as usize; + 4 => { + // String + if pos + 4 > data.len() { + return Err(LoadError::UnexpectedEof); + } + let len = u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap()) as usize; pos += 4; - if pos + len > data.len() { return Err(LoadError::UnexpectedEof); } - let s = String::from_utf8_lossy(&data[pos..pos+len]).into_owned(); + if pos + len > data.len() { + return Err(LoadError::UnexpectedEof); + } + let s = String::from_utf8_lossy(&data[pos..pos + len]).into_owned(); cp.push(ConstantPoolEntry::String(s)); pos += len; } - 5 => { // Int32 - if pos + 4 > data.len() { return Err(LoadError::UnexpectedEof); } - let val = i32::from_le_bytes(data[pos..pos+4].try_into().unwrap()); + 5 => { + // Int32 + if pos + 4 > data.len() { + return Err(LoadError::UnexpectedEof); + } + let val = i32::from_le_bytes(data[pos..pos + 4].try_into().unwrap()); cp.push(ConstantPoolEntry::Int32(val)); pos += 4; } @@ -366,18 +416,18 @@ fn parse_functions(data: &[u8]) -> Result, LoadError> { let count = u32::from_le_bytes([data[0], data[1], data[2], data[3]]) as usize; let mut functions = Vec::with_capacity(count); let mut pos = 4; - + for _ in 0..count { if pos + 16 > data.len() { return Err(LoadError::UnexpectedEof); } - let code_offset = u32::from_le_bytes(data[pos..pos+4].try_into().unwrap()); - let code_len = u32::from_le_bytes(data[pos+4..pos+8].try_into().unwrap()); - let param_slots = u16::from_le_bytes(data[pos+8..pos+10].try_into().unwrap()); - let local_slots = u16::from_le_bytes(data[pos+10..pos+12].try_into().unwrap()); - let return_slots = u16::from_le_bytes(data[pos+12..pos+14].try_into().unwrap()); - let max_stack_slots = u16::from_le_bytes(data[pos+14..pos+16].try_into().unwrap()); - + let code_offset = u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap()); + let code_len = u32::from_le_bytes(data[pos + 4..pos + 8].try_into().unwrap()); + let param_slots = u16::from_le_bytes(data[pos + 8..pos + 10].try_into().unwrap()); + let local_slots = u16::from_le_bytes(data[pos + 10..pos + 12].try_into().unwrap()); + let return_slots = u16::from_le_bytes(data[pos + 12..pos + 14].try_into().unwrap()); + let max_stack_slots = u16::from_le_bytes(data[pos + 14..pos + 16].try_into().unwrap()); + functions.push(FunctionMeta { code_offset, code_len, @@ -398,47 +448,47 @@ fn parse_debug_section(data: &[u8]) -> Result { if data.len() < 8 { return Err(LoadError::MalformedSection); } - + let mut pos = 0; - + // PC to Span table - let span_count = u32::from_le_bytes(data[pos..pos+4].try_into().unwrap()) as usize; + let span_count = u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap()) as usize; pos += 4; let mut pc_to_span = Vec::with_capacity(span_count); for _ in 0..span_count { if pos + 16 > data.len() { return Err(LoadError::UnexpectedEof); } - let pc = u32::from_le_bytes(data[pos..pos+4].try_into().unwrap()); - let file_id = u32::from_le_bytes(data[pos+4..pos+8].try_into().unwrap()); - let start = u32::from_le_bytes(data[pos+8..pos+12].try_into().unwrap()); - let end = u32::from_le_bytes(data[pos+12..pos+16].try_into().unwrap()); + let pc = u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap()); + let file_id = u32::from_le_bytes(data[pos + 4..pos + 8].try_into().unwrap()); + let start = u32::from_le_bytes(data[pos + 8..pos + 12].try_into().unwrap()); + let end = u32::from_le_bytes(data[pos + 12..pos + 16].try_into().unwrap()); pc_to_span.push((pc, SourceSpan { file_id, start, end })); pos += 16; } - + // Function names table if pos + 4 > data.len() { return Err(LoadError::UnexpectedEof); } - let func_name_count = u32::from_le_bytes(data[pos..pos+4].try_into().unwrap()) as usize; + let func_name_count = u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap()) as usize; pos += 4; let mut function_names = Vec::with_capacity(func_name_count); for _ in 0..func_name_count { if pos + 8 > data.len() { return Err(LoadError::UnexpectedEof); } - let func_idx = u32::from_le_bytes(data[pos..pos+4].try_into().unwrap()); - let name_len = u32::from_le_bytes(data[pos+4..pos+8].try_into().unwrap()) as usize; + let func_idx = u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap()); + let name_len = u32::from_le_bytes(data[pos + 4..pos + 8].try_into().unwrap()) as usize; pos += 8; if pos + name_len > data.len() { return Err(LoadError::UnexpectedEof); } - let name = String::from_utf8_lossy(&data[pos..pos+name_len]).into_owned(); + let name = String::from_utf8_lossy(&data[pos..pos + name_len]).into_owned(); function_names.push((func_idx, name)); pos += name_len; } - + Ok(DebugInfo { pc_to_span, function_names }) } @@ -457,20 +507,19 @@ fn parse_exports(data: &[u8]) -> Result, LoadError> { if pos + 8 > data.len() { return Err(LoadError::UnexpectedEof); } - let func_idx = u32::from_le_bytes(data[pos..pos+4].try_into().unwrap()); - let name_len = u32::from_le_bytes(data[pos+4..pos+8].try_into().unwrap()) as usize; + let func_idx = u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap()); + let name_len = u32::from_le_bytes(data[pos + 4..pos + 8].try_into().unwrap()) as usize; pos += 8; if pos + name_len > data.len() { return Err(LoadError::UnexpectedEof); } - let symbol = String::from_utf8_lossy(&data[pos..pos+name_len]).into_owned(); + let symbol = String::from_utf8_lossy(&data[pos..pos + name_len]).into_owned(); exports.push(Export { symbol, func_idx }); pos += name_len; } Ok(exports) } - fn validate_module(module: &BytecodeModule) -> Result<(), LoadError> { for func in &module.functions { // Opcode stream bounds @@ -485,22 +534,35 @@ fn validate_module(module: &BytecodeModule) -> Result<(), LoadError> { if pos + 2 > module.code.len() { break; // Unexpected EOF in middle of opcode, maybe should be error } - let op_val = u16::from_le_bytes([module.code[pos], module.code[pos+1]]); + let op_val = u16::from_le_bytes([module.code[pos], module.code[pos + 1]]); let opcode = OpCode::try_from(op_val).map_err(|_| LoadError::InvalidOpcode)?; pos += 2; match opcode { OpCode::PushConst => { - if pos + 4 > module.code.len() { return Err(LoadError::UnexpectedEof); } - let idx = u32::from_le_bytes(module.code[pos..pos+4].try_into().unwrap()) as usize; + if pos + 4 > module.code.len() { + return Err(LoadError::UnexpectedEof); + } + let idx = + u32::from_le_bytes(module.code[pos..pos + 4].try_into().unwrap()) as usize; if idx >= module.const_pool.len() { return Err(LoadError::InvalidConstIndex); } pos += 4; } - OpCode::PushI32 | OpCode::PushBounded | OpCode::Jmp | OpCode::JmpIfFalse | OpCode::JmpIfTrue - | OpCode::GetGlobal | OpCode::SetGlobal | OpCode::GetLocal | OpCode::SetLocal - | OpCode::PopN | OpCode::Syscall | OpCode::GateLoad | OpCode::GateStore => { + OpCode::PushI32 + | OpCode::PushBounded + | OpCode::Jmp + | OpCode::JmpIfFalse + | OpCode::JmpIfTrue + | OpCode::GetGlobal + | OpCode::SetGlobal + | OpCode::GetLocal + | OpCode::SetLocal + | OpCode::PopN + | OpCode::Syscall + | OpCode::GateLoad + | OpCode::GateStore => { pos += 4; } OpCode::PushI64 | OpCode::PushF64 => { @@ -567,7 +629,7 @@ mod tests { data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(&80u32.to_le_bytes()); data.extend_from_slice(&32u32.to_le_bytes()); - + // Ensure data is long enough for the offsets data.resize(256, 0); @@ -581,7 +643,7 @@ mod tests { data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&64u32.to_le_bytes()); data.extend_from_slice(&1000u32.to_le_bytes()); - + data.resize(256, 0); assert_eq!(BytecodeLoader::load(&data), Err(LoadError::SectionOutOfBounds)); @@ -594,21 +656,21 @@ mod tests { data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(&64u32.to_le_bytes()); data.extend_from_slice(&20u32.to_le_bytes()); - + // Section 2: Code, Kind 2, Offset 128, Length 10 data.extend_from_slice(&2u32.to_le_bytes()); data.extend_from_slice(&128u32.to_le_bytes()); data.extend_from_slice(&10u32.to_le_bytes()); data.resize(256, 0); - + // Setup functions section let func_data_start = 64; - data[func_data_start..func_data_start+4].copy_from_slice(&1u32.to_le_bytes()); // 1 function + data[func_data_start..func_data_start + 4].copy_from_slice(&1u32.to_le_bytes()); // 1 function let entry_start = func_data_start + 4; - data[entry_start..entry_start+4].copy_from_slice(&5u32.to_le_bytes()); // code_offset = 5 - data[entry_start+4..entry_start+8].copy_from_slice(&10u32.to_le_bytes()); // code_len = 10 - // 5 + 10 = 15 > 10 (code section length) + data[entry_start..entry_start + 4].copy_from_slice(&5u32.to_le_bytes()); // code_offset = 5 + data[entry_start + 4..entry_start + 8].copy_from_slice(&10u32.to_le_bytes()); // code_len = 10 + // 5 + 10 = 15 > 10 (code section length) assert_eq!(BytecodeLoader::load(&data), Err(LoadError::InvalidFunctionIndex)); } @@ -620,17 +682,17 @@ mod tests { data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&64u32.to_le_bytes()); data.extend_from_slice(&4u32.to_le_bytes()); - + // Section 2: Code, Kind 2, Offset 128, Length 6 (PushConst 0) data.extend_from_slice(&2u32.to_le_bytes()); data.extend_from_slice(&128u32.to_le_bytes()); data.extend_from_slice(&6u32.to_le_bytes()); data.resize(256, 0); - + // Setup empty CP data[64..68].copy_from_slice(&0u32.to_le_bytes()); - + // Setup code with PushConst 0 data[128..130].copy_from_slice(&(OpCode::PushConst as u16).to_le_bytes()); data[130..134].copy_from_slice(&0u32.to_le_bytes()); diff --git a/crates/console/prometeu-bytecode/src/opcode.rs b/crates/console/prometeu-bytecode/src/opcode.rs index 21e3e6c5..42e68102 100644 --- a/crates/console/prometeu-bytecode/src/opcode.rs +++ b/crates/console/prometeu-bytecode/src/opcode.rs @@ -1,5 +1,5 @@ /// Represents a single instruction in the Prometeu Virtual Machine. -/// +/// /// Each OpCode is encoded as a 16-bit unsigned integer (u16) in the bytecode. /// The PVM is a stack-based machine, meaning most instructions take their /// operands from the top of the stack and push their results back onto it. @@ -7,7 +7,6 @@ #[repr(u16)] pub enum OpCode { // --- 6.1 Execution Control --- - /// No operation. Does nothing for 1 cycle. Nop = 0x00, /// Stops the Virtual Machine execution immediately. @@ -27,7 +26,6 @@ pub enum OpCode { Trap = 0x05, // --- 6.2 Stack Manipulation --- - /// Loads a constant from the Constant Pool into the stack. /// Operand: index (u32) /// Stack: [] -> [value] @@ -62,7 +60,6 @@ pub enum OpCode { PushBounded = 0x19, // --- 6.3 Arithmetic --- - /// Adds the two top values (a + b). /// Stack: [a, b] -> [result] Add = 0x20, @@ -86,7 +83,6 @@ pub enum OpCode { IntToBoundChecked = 0x26, // --- 6.4 Comparison and Logic --- - /// Checks if a equals b. /// Stack: [a, b] -> [bool] Eq = 0x30, @@ -134,7 +130,6 @@ pub enum OpCode { Neg = 0x3E, // --- 6.5 Variables --- - /// Loads a value from a global variable slot. /// Operand: slot_index (u32) /// Stack: [] -> [value] @@ -153,7 +148,6 @@ pub enum OpCode { SetLocal = 0x43, // --- 6.6 Functions --- - /// Calls a function by its index in the function table. /// Operand: func_id (u32) /// Stack: [arg0, arg1, ...] -> [return_slots...] @@ -167,7 +161,6 @@ pub enum OpCode { PopScope = 0x53, // --- 6.7 HIP (Heap Interface Protocol) --- - /// Allocates `slots` slots on the heap with the given `type_id`. /// Operands: type_id (u32), slots (u32) /// Stack: [] -> [gate] @@ -208,7 +201,6 @@ pub enum OpCode { GateRelease = 0x6A, // --- 6.8 Peripherals and System --- - /// Invokes a system function (Firmware/OS). /// Operand: syscall_id (u32) /// Stack: [args...] -> [results...] (depends on syscall) diff --git a/crates/console/prometeu-bytecode/src/opcode_spec.rs b/crates/console/prometeu-bytecode/src/opcode_spec.rs index 81f595ff..63d69063 100644 --- a/crates/console/prometeu-bytecode/src/opcode_spec.rs +++ b/crates/console/prometeu-bytecode/src/opcode_spec.rs @@ -20,65 +20,537 @@ pub trait OpCodeSpecExt { impl OpCodeSpecExt for OpCode { fn spec(&self) -> OpcodeSpec { match self { - OpCode::Nop => OpcodeSpec { name: "NOP", imm_bytes: 0, pops: 0, pushes: 0, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::Halt => OpcodeSpec { name: "HALT", imm_bytes: 0, pops: 0, pushes: 0, is_branch: false, is_terminator: true, may_trap: false }, - OpCode::Jmp => OpcodeSpec { name: "JMP", imm_bytes: 4, pops: 0, pushes: 0, is_branch: true, is_terminator: true, may_trap: false }, - OpCode::JmpIfFalse => OpcodeSpec { name: "JMP_IF_FALSE", imm_bytes: 4, pops: 1, pushes: 0, is_branch: true, is_terminator: false, may_trap: true }, - OpCode::JmpIfTrue => OpcodeSpec { name: "JMP_IF_TRUE", imm_bytes: 4, pops: 1, pushes: 0, is_branch: true, is_terminator: false, may_trap: true }, - OpCode::Trap => OpcodeSpec { name: "TRAP", imm_bytes: 0, pops: 0, pushes: 0, is_branch: false, is_terminator: true, may_trap: true }, - OpCode::PushConst => OpcodeSpec { name: "PUSH_CONST", imm_bytes: 4, pops: 0, pushes: 1, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::Pop => OpcodeSpec { name: "POP", imm_bytes: 0, pops: 1, pushes: 0, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::PopN => OpcodeSpec { name: "POP_N", imm_bytes: 4, pops: 0, pushes: 0, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::Dup => OpcodeSpec { name: "DUP", imm_bytes: 0, pops: 1, pushes: 2, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::Swap => OpcodeSpec { name: "SWAP", imm_bytes: 0, pops: 2, pushes: 2, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::PushI64 => OpcodeSpec { name: "PUSH_I64", imm_bytes: 8, pops: 0, pushes: 1, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::PushF64 => OpcodeSpec { name: "PUSH_F64", imm_bytes: 8, pops: 0, pushes: 1, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::PushBool => OpcodeSpec { name: "PUSH_BOOL", imm_bytes: 1, pops: 0, pushes: 1, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::PushI32 => OpcodeSpec { name: "PUSH_I32", imm_bytes: 4, pops: 0, pushes: 1, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::PushBounded => OpcodeSpec { name: "PUSH_BOUNDED", imm_bytes: 4, pops: 0, pushes: 1, is_branch: false, is_terminator: false, may_trap: true }, - OpCode::Add => OpcodeSpec { name: "ADD", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: true }, - OpCode::Sub => OpcodeSpec { name: "SUB", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: true }, - OpCode::Mul => OpcodeSpec { name: "MUL", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: true }, - OpCode::Div => OpcodeSpec { name: "DIV", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: true }, - OpCode::Mod => OpcodeSpec { name: "MOD", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: true }, - OpCode::BoundToInt => OpcodeSpec { name: "BOUND_TO_INT", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::IntToBoundChecked => OpcodeSpec { name: "INT_TO_BOUND_CHECKED", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: true }, - OpCode::Eq => OpcodeSpec { name: "EQ", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::Neq => OpcodeSpec { name: "NEQ", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::Lt => OpcodeSpec { name: "LT", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::Gt => OpcodeSpec { name: "GT", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::And => OpcodeSpec { name: "AND", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::Or => OpcodeSpec { name: "OR", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::Not => OpcodeSpec { name: "NOT", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::BitAnd => OpcodeSpec { name: "BIT_AND", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::BitOr => OpcodeSpec { name: "BIT_OR", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::BitXor => OpcodeSpec { name: "BIT_XOR", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::Shl => OpcodeSpec { name: "SHL", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::Shr => OpcodeSpec { name: "SHR", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::Lte => OpcodeSpec { name: "LTE", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::Gte => OpcodeSpec { name: "GTE", imm_bytes: 0, pops: 2, pushes: 1, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::Neg => OpcodeSpec { name: "NEG", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::GetGlobal => OpcodeSpec { name: "GET_GLOBAL", imm_bytes: 4, pops: 0, pushes: 1, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::SetGlobal => OpcodeSpec { name: "SET_GLOBAL", imm_bytes: 4, pops: 1, pushes: 0, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::GetLocal => OpcodeSpec { name: "GET_LOCAL", imm_bytes: 4, pops: 0, pushes: 1, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::SetLocal => OpcodeSpec { name: "SET_LOCAL", imm_bytes: 4, pops: 1, pushes: 0, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::Call => OpcodeSpec { name: "CALL", imm_bytes: 4, pops: 0, pushes: 0, is_branch: false, is_terminator: false, may_trap: true }, - OpCode::Ret => OpcodeSpec { name: "RET", imm_bytes: 0, pops: 0, pushes: 0, is_branch: false, is_terminator: true, may_trap: false }, - OpCode::PushScope => OpcodeSpec { name: "PUSH_SCOPE", imm_bytes: 0, pops: 0, pushes: 0, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::PopScope => OpcodeSpec { name: "POP_SCOPE", imm_bytes: 0, pops: 0, pushes: 0, is_branch: false, is_terminator: false, may_trap: false }, - OpCode::Alloc => OpcodeSpec { name: "ALLOC", imm_bytes: 8, pops: 0, pushes: 1, is_branch: false, is_terminator: false, may_trap: true }, - OpCode::GateLoad => OpcodeSpec { name: "GATE_LOAD", imm_bytes: 4, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: true }, - OpCode::GateStore => OpcodeSpec { name: "GATE_STORE", imm_bytes: 4, pops: 2, pushes: 0, is_branch: false, is_terminator: false, may_trap: true }, - OpCode::GateBeginPeek => OpcodeSpec { name: "GATE_BEGIN_PEEK", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: true }, - OpCode::GateEndPeek => OpcodeSpec { name: "GATE_END_PEEK", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: true }, - OpCode::GateBeginBorrow => OpcodeSpec { name: "GATE_BEGIN_BORROW", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: true }, - OpCode::GateEndBorrow => OpcodeSpec { name: "GATE_END_BORROW", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: true }, - OpCode::GateBeginMutate => OpcodeSpec { name: "GATE_BEGIN_MUTATE", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: true }, - OpCode::GateEndMutate => OpcodeSpec { name: "GATE_END_MUTATE", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: true }, - OpCode::GateRetain => OpcodeSpec { name: "GATE_RETAIN", imm_bytes: 0, pops: 1, pushes: 1, is_branch: false, is_terminator: false, may_trap: true }, - OpCode::GateRelease => OpcodeSpec { name: "GATE_RELEASE", imm_bytes: 0, pops: 1, pushes: 0, is_branch: false, is_terminator: false, may_trap: true }, - OpCode::Syscall => OpcodeSpec { name: "SYSCALL", imm_bytes: 4, pops: 0, pushes: 0, is_branch: false, is_terminator: false, may_trap: true }, - OpCode::FrameSync => OpcodeSpec { name: "FRAME_SYNC", imm_bytes: 0, pops: 0, pushes: 0, is_branch: false, is_terminator: false, may_trap: false }, + OpCode::Nop => OpcodeSpec { + name: "NOP", + imm_bytes: 0, + pops: 0, + pushes: 0, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::Halt => OpcodeSpec { + name: "HALT", + imm_bytes: 0, + pops: 0, + pushes: 0, + is_branch: false, + is_terminator: true, + may_trap: false, + }, + OpCode::Jmp => OpcodeSpec { + name: "JMP", + imm_bytes: 4, + pops: 0, + pushes: 0, + is_branch: true, + is_terminator: true, + may_trap: false, + }, + OpCode::JmpIfFalse => OpcodeSpec { + name: "JMP_IF_FALSE", + imm_bytes: 4, + pops: 1, + pushes: 0, + is_branch: true, + is_terminator: false, + may_trap: true, + }, + OpCode::JmpIfTrue => OpcodeSpec { + name: "JMP_IF_TRUE", + imm_bytes: 4, + pops: 1, + pushes: 0, + is_branch: true, + is_terminator: false, + may_trap: true, + }, + OpCode::Trap => OpcodeSpec { + name: "TRAP", + imm_bytes: 0, + pops: 0, + pushes: 0, + is_branch: false, + is_terminator: true, + may_trap: true, + }, + OpCode::PushConst => OpcodeSpec { + name: "PUSH_CONST", + imm_bytes: 4, + pops: 0, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::Pop => OpcodeSpec { + name: "POP", + imm_bytes: 0, + pops: 1, + pushes: 0, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::PopN => OpcodeSpec { + name: "POP_N", + imm_bytes: 4, + pops: 0, + pushes: 0, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::Dup => OpcodeSpec { + name: "DUP", + imm_bytes: 0, + pops: 1, + pushes: 2, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::Swap => OpcodeSpec { + name: "SWAP", + imm_bytes: 0, + pops: 2, + pushes: 2, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::PushI64 => OpcodeSpec { + name: "PUSH_I64", + imm_bytes: 8, + pops: 0, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::PushF64 => OpcodeSpec { + name: "PUSH_F64", + imm_bytes: 8, + pops: 0, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::PushBool => OpcodeSpec { + name: "PUSH_BOOL", + imm_bytes: 1, + pops: 0, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::PushI32 => OpcodeSpec { + name: "PUSH_I32", + imm_bytes: 4, + pops: 0, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::PushBounded => OpcodeSpec { + name: "PUSH_BOUNDED", + imm_bytes: 4, + pops: 0, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: true, + }, + OpCode::Add => OpcodeSpec { + name: "ADD", + imm_bytes: 0, + pops: 2, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: true, + }, + OpCode::Sub => OpcodeSpec { + name: "SUB", + imm_bytes: 0, + pops: 2, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: true, + }, + OpCode::Mul => OpcodeSpec { + name: "MUL", + imm_bytes: 0, + pops: 2, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: true, + }, + OpCode::Div => OpcodeSpec { + name: "DIV", + imm_bytes: 0, + pops: 2, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: true, + }, + OpCode::Mod => OpcodeSpec { + name: "MOD", + imm_bytes: 0, + pops: 2, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: true, + }, + OpCode::BoundToInt => OpcodeSpec { + name: "BOUND_TO_INT", + imm_bytes: 0, + pops: 1, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::IntToBoundChecked => OpcodeSpec { + name: "INT_TO_BOUND_CHECKED", + imm_bytes: 0, + pops: 1, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: true, + }, + OpCode::Eq => OpcodeSpec { + name: "EQ", + imm_bytes: 0, + pops: 2, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::Neq => OpcodeSpec { + name: "NEQ", + imm_bytes: 0, + pops: 2, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::Lt => OpcodeSpec { + name: "LT", + imm_bytes: 0, + pops: 2, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::Gt => OpcodeSpec { + name: "GT", + imm_bytes: 0, + pops: 2, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::And => OpcodeSpec { + name: "AND", + imm_bytes: 0, + pops: 2, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::Or => OpcodeSpec { + name: "OR", + imm_bytes: 0, + pops: 2, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::Not => OpcodeSpec { + name: "NOT", + imm_bytes: 0, + pops: 1, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::BitAnd => OpcodeSpec { + name: "BIT_AND", + imm_bytes: 0, + pops: 2, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::BitOr => OpcodeSpec { + name: "BIT_OR", + imm_bytes: 0, + pops: 2, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::BitXor => OpcodeSpec { + name: "BIT_XOR", + imm_bytes: 0, + pops: 2, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::Shl => OpcodeSpec { + name: "SHL", + imm_bytes: 0, + pops: 2, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::Shr => OpcodeSpec { + name: "SHR", + imm_bytes: 0, + pops: 2, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::Lte => OpcodeSpec { + name: "LTE", + imm_bytes: 0, + pops: 2, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::Gte => OpcodeSpec { + name: "GTE", + imm_bytes: 0, + pops: 2, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::Neg => OpcodeSpec { + name: "NEG", + imm_bytes: 0, + pops: 1, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::GetGlobal => OpcodeSpec { + name: "GET_GLOBAL", + imm_bytes: 4, + pops: 0, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::SetGlobal => OpcodeSpec { + name: "SET_GLOBAL", + imm_bytes: 4, + pops: 1, + pushes: 0, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::GetLocal => OpcodeSpec { + name: "GET_LOCAL", + imm_bytes: 4, + pops: 0, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::SetLocal => OpcodeSpec { + name: "SET_LOCAL", + imm_bytes: 4, + pops: 1, + pushes: 0, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::Call => OpcodeSpec { + name: "CALL", + imm_bytes: 4, + pops: 0, + pushes: 0, + is_branch: false, + is_terminator: false, + may_trap: true, + }, + OpCode::Ret => OpcodeSpec { + name: "RET", + imm_bytes: 0, + pops: 0, + pushes: 0, + is_branch: false, + is_terminator: true, + may_trap: false, + }, + OpCode::PushScope => OpcodeSpec { + name: "PUSH_SCOPE", + imm_bytes: 0, + pops: 0, + pushes: 0, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::PopScope => OpcodeSpec { + name: "POP_SCOPE", + imm_bytes: 0, + pops: 0, + pushes: 0, + is_branch: false, + is_terminator: false, + may_trap: false, + }, + OpCode::Alloc => OpcodeSpec { + name: "ALLOC", + imm_bytes: 8, + pops: 0, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: true, + }, + OpCode::GateLoad => OpcodeSpec { + name: "GATE_LOAD", + imm_bytes: 4, + pops: 1, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: true, + }, + OpCode::GateStore => OpcodeSpec { + name: "GATE_STORE", + imm_bytes: 4, + pops: 2, + pushes: 0, + is_branch: false, + is_terminator: false, + may_trap: true, + }, + OpCode::GateBeginPeek => OpcodeSpec { + name: "GATE_BEGIN_PEEK", + imm_bytes: 0, + pops: 1, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: true, + }, + OpCode::GateEndPeek => OpcodeSpec { + name: "GATE_END_PEEK", + imm_bytes: 0, + pops: 1, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: true, + }, + OpCode::GateBeginBorrow => OpcodeSpec { + name: "GATE_BEGIN_BORROW", + imm_bytes: 0, + pops: 1, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: true, + }, + OpCode::GateEndBorrow => OpcodeSpec { + name: "GATE_END_BORROW", + imm_bytes: 0, + pops: 1, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: true, + }, + OpCode::GateBeginMutate => OpcodeSpec { + name: "GATE_BEGIN_MUTATE", + imm_bytes: 0, + pops: 1, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: true, + }, + OpCode::GateEndMutate => OpcodeSpec { + name: "GATE_END_MUTATE", + imm_bytes: 0, + pops: 1, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: true, + }, + OpCode::GateRetain => OpcodeSpec { + name: "GATE_RETAIN", + imm_bytes: 0, + pops: 1, + pushes: 1, + is_branch: false, + is_terminator: false, + may_trap: true, + }, + OpCode::GateRelease => OpcodeSpec { + name: "GATE_RELEASE", + imm_bytes: 0, + pops: 1, + pushes: 0, + is_branch: false, + is_terminator: false, + may_trap: true, + }, + OpCode::Syscall => OpcodeSpec { + name: "SYSCALL", + imm_bytes: 4, + pops: 0, + pushes: 0, + is_branch: false, + is_terminator: false, + may_trap: true, + }, + OpCode::FrameSync => OpcodeSpec { + name: "FRAME_SYNC", + imm_bytes: 0, + pops: 0, + pushes: 0, + is_branch: false, + is_terminator: false, + may_trap: false, + }, } } } diff --git a/crates/console/prometeu-bytecode/src/program_image.rs b/crates/console/prometeu-bytecode/src/program_image.rs index b045904e..d40394db 100644 --- a/crates/console/prometeu-bytecode/src/program_image.rs +++ b/crates/console/prometeu-bytecode/src/program_image.rs @@ -1,16 +1,16 @@ use crate::abi::TrapInfo; -use std::collections::HashMap; -use std::sync::Arc; use crate::model::{BytecodeModule, ConstantPoolEntry, DebugInfo, Export, FunctionMeta}; use crate::value::Value; +use std::collections::HashMap; +use std::sync::Arc; /// Represents a fully linked, executable PBS program image. /// -/// Under the Prometeu architecture, the ProgramImage is a "closed-world" artifact -/// produced by the compiler. All linking, relocation, and symbol resolution +/// Under the Prometeu architecture, the ProgramImage is a "closed-world" artifact +/// produced by the compiler. All linking, relocation, and symbol resolution /// MUST be performed by the compiler before this image is created. /// -/// The runtime (VM) assumes this image is authoritative and performs no +/// The runtime (VM) assumes this image is authoritative and performs no /// additional linking or fixups. #[derive(Debug, Clone, Default)] pub struct ProgramImage { @@ -22,7 +22,13 @@ pub struct ProgramImage { } impl ProgramImage { - pub fn new(rom: Vec, constant_pool: Vec, functions: Vec, debug_info: Option, exports: HashMap) -> Self { + pub fn new( + rom: Vec, + constant_pool: Vec, + functions: Vec, + debug_info: Option, + exports: HashMap, + ) -> Self { Self { rom: Arc::from(rom), constant_pool: Arc::from(constant_pool), @@ -33,33 +39,27 @@ impl ProgramImage { } pub fn create_trap(&self, code: u32, opcode: u16, mut message: String, pc: u32) -> TrapInfo { - let span = self.debug_info.as_ref().and_then(|di| { - di.pc_to_span.iter().find(|(p, _)| *p == pc).map(|(_, s)| s.clone()) - }); + let span = self + .debug_info + .as_ref() + .and_then(|di| di.pc_to_span.iter().find(|(p, _)| *p == pc).map(|(_, s)| s.clone())); if let Some(func_idx) = self.find_function_index(pc) { if let Some(func_name) = self.get_function_name(func_idx) { message = format!("{} (in function {})", message, func_name); } } - - TrapInfo { - code, - opcode, - message, - pc, - span, - } + + TrapInfo { code, opcode, message, pc, span } } pub fn find_function_index(&self, pc: u32) -> Option { - self.functions.iter().position(|f| { - pc >= f.code_offset && pc < (f.code_offset + f.code_len) - }) + self.functions.iter().position(|f| pc >= f.code_offset && pc < (f.code_offset + f.code_len)) } pub fn get_function_name(&self, func_idx: usize) -> Option<&str> { - self.debug_info.as_ref() + self.debug_info + .as_ref() .and_then(|di| di.function_names.iter().find(|(idx, _)| *idx as usize == func_idx)) .map(|(_, name)| name.as_str()) } @@ -67,49 +67,50 @@ impl ProgramImage { impl From for ProgramImage { fn from(module: BytecodeModule) -> Self { - let constant_pool: Vec = module.const_pool.iter().map(|entry| { - match entry { + let constant_pool: Vec = module + .const_pool + .iter() + .map(|entry| match entry { ConstantPoolEntry::Null => Value::Null, ConstantPoolEntry::Int64(v) => Value::Int64(*v), ConstantPoolEntry::Float64(v) => Value::Float(*v), ConstantPoolEntry::Boolean(v) => Value::Boolean(*v), ConstantPoolEntry::String(v) => Value::String(v.clone()), ConstantPoolEntry::Int32(v) => Value::Int32(*v), - } - }).collect(); + }) + .collect(); let mut exports = HashMap::new(); for export in module.exports { exports.insert(export.symbol, export.func_idx); } - ProgramImage::new( - module.code, - constant_pool, - module.functions, - module.debug_info, - exports, - ) + ProgramImage::new(module.code, constant_pool, module.functions, module.debug_info, exports) } } impl From for BytecodeModule { fn from(program: ProgramImage) -> Self { - let const_pool = program.constant_pool.iter().map(|v| match v { - Value::Null => ConstantPoolEntry::Null, - Value::Int64(v) => ConstantPoolEntry::Int64(*v), - Value::Float(v) => ConstantPoolEntry::Float64(*v), - Value::Boolean(v) => ConstantPoolEntry::Boolean(*v), - Value::String(v) => ConstantPoolEntry::String(v.clone()), - Value::Int32(v) => ConstantPoolEntry::Int32(*v), - Value::Bounded(v) => ConstantPoolEntry::Int32(*v as i32), - Value::Gate(_) => ConstantPoolEntry::Null, - }).collect(); + let const_pool = program + .constant_pool + .iter() + .map(|v| match v { + Value::Null => ConstantPoolEntry::Null, + Value::Int64(v) => ConstantPoolEntry::Int64(*v), + Value::Float(v) => ConstantPoolEntry::Float64(*v), + Value::Boolean(v) => ConstantPoolEntry::Boolean(*v), + Value::String(v) => ConstantPoolEntry::String(v.clone()), + Value::Int32(v) => ConstantPoolEntry::Int32(*v), + Value::Bounded(v) => ConstantPoolEntry::Int32(*v as i32), + Value::Gate(_) => ConstantPoolEntry::Null, + }) + .collect(); - let exports = program.exports.iter().map(|(symbol, &func_idx)| Export { - symbol: symbol.clone(), - func_idx, - }).collect(); + let exports = program + .exports + .iter() + .map(|(symbol, &func_idx)| Export { symbol: symbol.clone(), func_idx }) + .collect(); BytecodeModule { version: 0, diff --git a/crates/console/prometeu-bytecode/src/value.rs b/crates/console/prometeu-bytecode/src/value.rs index 4f4f6e5d..7604e678 100644 --- a/crates/console/prometeu-bytecode/src/value.rs +++ b/crates/console/prometeu-bytecode/src/value.rs @@ -2,10 +2,10 @@ use serde::{Deserialize, Serialize}; use std::cmp::Ordering; /// Represents any piece of data that can be stored on the VM stack or in globals. -/// -/// The PVM is "dynamically typed" at the bytecode level, meaning a single -/// `Value` enum can hold different primitive types. The VM performs -/// automatic type promotion (e.g., adding an Int32 to a Float64 results +/// +/// The PVM is "dynamically typed" at the bytecode level, meaning a single +/// `Value` enum can hold different primitive types. The VM performs +/// automatic type promotion (e.g., adding an Int32 to a Float64 results /// in a Float64) to ensure mathematical correctness. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] diff --git a/crates/console/prometeu-drivers/src/asset.rs b/crates/console/prometeu-drivers/src/asset.rs index 919be4e7..3f246638 100644 --- a/crates/console/prometeu-drivers/src/asset.rs +++ b/crates/console/prometeu-drivers/src/asset.rs @@ -1,14 +1,16 @@ -use std::collections::HashMap; -use std::sync::{Arc, Mutex, RwLock}; -use std::thread; -use std::time::Instant; -use prometeu_hal::asset::{AssetEntry, BankStats, BankType, HandleId, LoadStatus, PreloadEntry, SlotRef, SlotStats}; +use crate::memory_banks::{SoundBankPoolInstaller, TileBankPoolInstaller}; use prometeu_hal::AssetBridge; +use prometeu_hal::asset::{ + AssetEntry, BankStats, BankType, HandleId, LoadStatus, PreloadEntry, SlotRef, SlotStats, +}; use prometeu_hal::color::Color; use prometeu_hal::sample::Sample; use prometeu_hal::sound_bank::SoundBank; use prometeu_hal::tile_bank::{TileBank, TileSize}; -use crate::memory_banks::{SoundBankPoolInstaller, TileBankPoolInstaller}; +use std::collections::HashMap; +use std::sync::{Arc, Mutex, RwLock}; +use std::thread; +use std::time::Instant; /// Resident metadata for a decoded/materialized asset inside a BankPolicy. #[derive(Debug)] @@ -21,7 +23,6 @@ pub struct ResidentEntry { // /// Pin count (optional): if > 0, entry should not be evicted by policy. // pub pins: u32, - /// Telemetry / profiling fields (optional but useful). pub loads: u64, pub last_used: Instant, @@ -129,16 +130,41 @@ struct LoadHandleInfo { } impl AssetBridge for AssetManager { - fn initialize_for_cartridge(&self, assets: Vec, preload: Vec, assets_data: Vec) { self.initialize_for_cartridge(assets, preload, assets_data) } - fn load(&self, asset_name: &str, slot: SlotRef) -> Result { self.load(asset_name, slot) } - fn status(&self, handle: HandleId) -> LoadStatus { self.status(handle) } - fn commit(&self, handle: HandleId) { self.commit(handle) } - fn cancel(&self, handle: HandleId) { self.cancel(handle) } - fn apply_commits(&self) { self.apply_commits() } - fn bank_info(&self, kind: BankType) -> BankStats { self.bank_info(kind) } - fn slot_info(&self, slot: SlotRef) -> SlotStats { self.slot_info(slot) } - fn find_slot_by_name(&self, asset_name: &str, kind: BankType) -> Option { self.find_slot_by_name(asset_name, kind) } - fn shutdown(&self) { self.shutdown() } + fn initialize_for_cartridge( + &self, + assets: Vec, + preload: Vec, + assets_data: Vec, + ) { + self.initialize_for_cartridge(assets, preload, assets_data) + } + fn load(&self, asset_name: &str, slot: SlotRef) -> Result { + self.load(asset_name, slot) + } + fn status(&self, handle: HandleId) -> LoadStatus { + self.status(handle) + } + fn commit(&self, handle: HandleId) { + self.commit(handle) + } + fn cancel(&self, handle: HandleId) { + self.cancel(handle) + } + fn apply_commits(&self) { + self.apply_commits() + } + fn bank_info(&self, kind: BankType) -> BankStats { + self.bank_info(kind) + } + fn slot_info(&self, slot: SlotRef) -> SlotStats { + self.slot_info(slot) + } + fn find_slot_by_name(&self, asset_name: &str, kind: BankType) -> Option { + self.find_slot_by_name(asset_name, kind) + } + fn shutdown(&self) { + self.shutdown() + } } impl AssetManager { @@ -171,7 +197,12 @@ impl AssetManager { } } - pub fn initialize_for_cartridge(&self, assets: Vec, preload: Vec, assets_data: Vec) { + pub fn initialize_for_cartridge( + &self, + assets: Vec, + preload: Vec, + assets_data: Vec, + ) { self.shutdown(); { let mut asset_map = self.assets.write().unwrap(); @@ -190,45 +221,70 @@ impl AssetManager { let entry_opt = { let assets = self.assets.read().unwrap(); let name_to_id = self.name_to_id.read().unwrap(); - name_to_id.get(&item.asset_name) - .and_then(|id| assets.get(id)) - .cloned() + name_to_id.get(&item.asset_name).and_then(|id| assets.get(id)).cloned() }; if let Some(entry) = entry_opt { let slot_index = item.slot; match entry.bank_type { BankType::TILES => { - if let Ok(bank) = Self::perform_load_tile_bank(&entry, self.assets_data.clone()) { + if let Ok(bank) = + Self::perform_load_tile_bank(&entry, self.assets_data.clone()) + { let bank_arc = Arc::new(bank); - self.gfx_policy.put_resident(entry.asset_id, Arc::clone(&bank_arc), entry.decoded_size as usize); + self.gfx_policy.put_resident( + entry.asset_id, + Arc::clone(&bank_arc), + entry.decoded_size as usize, + ); self.gfx_installer.install_tile_bank(slot_index, bank_arc); let mut slots = self.gfx_slots.write().unwrap(); if slot_index < slots.len() { slots[slot_index] = Some(entry.asset_id); } - println!("[AssetManager] Preloaded tile asset '{}' (id: {}) into slot {}", entry.asset_name, entry.asset_id, slot_index); + println!( + "[AssetManager] Preloaded tile asset '{}' (id: {}) into slot {}", + entry.asset_name, entry.asset_id, slot_index + ); } else { - eprintln!("[AssetManager] Failed to preload tile asset '{}'", entry.asset_name); + eprintln!( + "[AssetManager] Failed to preload tile asset '{}'", + entry.asset_name + ); } } BankType::SOUNDS => { - if let Ok(bank) = Self::perform_load_sound_bank(&entry, self.assets_data.clone()) { + if let Ok(bank) = + Self::perform_load_sound_bank(&entry, self.assets_data.clone()) + { let bank_arc = Arc::new(bank); - self.sound_policy.put_resident(entry.asset_id, Arc::clone(&bank_arc), entry.decoded_size as usize); + self.sound_policy.put_resident( + entry.asset_id, + Arc::clone(&bank_arc), + entry.decoded_size as usize, + ); self.sound_installer.install_sound_bank(slot_index, bank_arc); let mut slots = self.sound_slots.write().unwrap(); if slot_index < slots.len() { slots[slot_index] = Some(entry.asset_id); } - println!("[AssetManager] Preloaded sound asset '{}' (id: {}) into slot {}", entry.asset_name, entry.asset_id, slot_index); + println!( + "[AssetManager] Preloaded sound asset '{}' (id: {}) into slot {}", + entry.asset_name, entry.asset_id, slot_index + ); } else { - eprintln!("[AssetManager] Failed to preload sound asset '{}'", entry.asset_name); + eprintln!( + "[AssetManager] Failed to preload sound asset '{}'", + entry.asset_name + ); } } } } else { - eprintln!("[AssetManager] Preload failed: asset '{}' not found in table", item.asset_name); + eprintln!( + "[AssetManager] Preload failed: asset '{}' not found in table", + item.asset_name + ); } } } @@ -237,7 +293,9 @@ impl AssetManager { let entry = { let assets = self.assets.read().unwrap(); let name_to_id = self.name_to_id.read().unwrap(); - let id = name_to_id.get(asset_name).ok_or_else(|| format!("Asset not found: {}", asset_name))?; + let id = name_to_id + .get(asset_name) + .ok_or_else(|| format!("Asset not found: {}", asset_name))?; assets.get(id).ok_or_else(|| format!("Asset ID {} not found in table", id))?.clone() }; let asset_id = entry.asset_id; @@ -256,31 +314,33 @@ impl AssetManager { if let Some(bank) = self.gfx_policy.get_resident(asset_id) { self.gfx_policy.stage(handle_id, bank); true - } else { false } + } else { + false + } } BankType::SOUNDS => { if let Some(bank) = self.sound_policy.get_resident(asset_id) { self.sound_policy.stage(handle_id, bank); true - } else { false } + } else { + false + } } }; if already_resident { - self.handles.write().unwrap().insert(handle_id, LoadHandleInfo { - _asset_id: asset_id, - slot, - status: LoadStatus::READY, - }); + self.handles.write().unwrap().insert( + handle_id, + LoadHandleInfo { _asset_id: asset_id, slot, status: LoadStatus::READY }, + ); return Ok(handle_id); } // Not resident, start loading - self.handles.write().unwrap().insert(handle_id, LoadHandleInfo { - _asset_id: asset_id, - slot, - status: LoadStatus::PENDING, - }); + self.handles.write().unwrap().insert( + handle_id, + LoadHandleInfo { _asset_id: asset_id, slot, status: LoadStatus::PENDING }, + ); let handles = self.handles.clone(); let assets_data = self.assets_data.clone(); @@ -319,7 +379,10 @@ impl AssetManager { existing.loads += 1; Arc::clone(&existing.value) } else { - let entry = ResidentEntry::new(Arc::clone(&bank_arc), entry_clone.decoded_size as usize); + let entry = ResidentEntry::new( + Arc::clone(&bank_arc), + entry_clone.decoded_size as usize, + ); map.insert(asset_id, entry); bank_arc } @@ -327,11 +390,15 @@ impl AssetManager { gfx_policy_staging.write().unwrap().insert(handle_id, resident_arc); let mut handles_map = handles.write().unwrap(); if let Some(h) = handles_map.get_mut(&handle_id) { - if h.status == LoadStatus::LOADING { h.status = LoadStatus::READY; } + if h.status == LoadStatus::LOADING { + h.status = LoadStatus::READY; + } } } else { let mut handles_map = handles.write().unwrap(); - if let Some(h) = handles_map.get_mut(&handle_id) { h.status = LoadStatus::ERROR; } + if let Some(h) = handles_map.get_mut(&handle_id) { + h.status = LoadStatus::ERROR; + } } } BankType::SOUNDS => { @@ -345,7 +412,10 @@ impl AssetManager { existing.loads += 1; Arc::clone(&existing.value) } else { - let entry = ResidentEntry::new(Arc::clone(&bank_arc), entry_clone.decoded_size as usize); + let entry = ResidentEntry::new( + Arc::clone(&bank_arc), + entry_clone.decoded_size as usize, + ); map.insert(asset_id, entry); bank_arc } @@ -353,11 +423,15 @@ impl AssetManager { sound_policy_staging.write().unwrap().insert(handle_id, resident_arc); let mut handles_map = handles.write().unwrap(); if let Some(h) = handles_map.get_mut(&handle_id) { - if h.status == LoadStatus::LOADING { h.status = LoadStatus::READY; } + if h.status == LoadStatus::LOADING { + h.status = LoadStatus::READY; + } } } else { let mut handles_map = handles.write().unwrap(); - if let Some(h) = handles_map.get_mut(&handle_id) { h.status = LoadStatus::ERROR; } + if let Some(h) = handles_map.get_mut(&handle_id) { + h.status = LoadStatus::ERROR; + } } } } @@ -366,16 +440,19 @@ impl AssetManager { Ok(handle_id) } - fn perform_load_tile_bank(entry: &AssetEntry, assets_data: Arc>>) -> Result { + fn perform_load_tile_bank( + entry: &AssetEntry, + assets_data: Arc>>, + ) -> Result { if entry.codec != "RAW" { return Err(format!("Unsupported codec: {}", entry.codec)); } let assets_data = assets_data.read().unwrap(); - + let start = entry.offset as usize; let end = start + entry.size as usize; - + if end > assets_data.len() { return Err("Asset offset/size out of bounds".to_string()); } @@ -383,9 +460,12 @@ impl AssetManager { let buffer = &assets_data[start..end]; // Decode TILEBANK metadata - let tile_size_val = entry.metadata.get("tile_size").and_then(|v| v.as_u64()).ok_or("Missing tile_size")?; - let width = entry.metadata.get("width").and_then(|v| v.as_u64()).ok_or("Missing width")? as usize; - let height = entry.metadata.get("height").and_then(|v| v.as_u64()).ok_or("Missing height")? as usize; + let tile_size_val = + entry.metadata.get("tile_size").and_then(|v| v.as_u64()).ok_or("Missing tile_size")?; + let width = + entry.metadata.get("width").and_then(|v| v.as_u64()).ok_or("Missing width")? as usize; + let height = + entry.metadata.get("height").and_then(|v| v.as_u64()).ok_or("Missing height")? as usize; let tile_size = match tile_size_val { 8 => TileSize::Size8, @@ -406,42 +486,41 @@ impl AssetManager { for p in 0..64 { for c in 0..16 { let offset = (p * 16 + c) * 2; - let color_raw = u16::from_le_bytes([palette_data[offset], palette_data[offset + 1]]); + let color_raw = + u16::from_le_bytes([palette_data[offset], palette_data[offset + 1]]); palettes[p][c] = Color(color_raw); } } - Ok(TileBank { - tile_size, - width, - height, - pixel_indices, - palettes, - }) + Ok(TileBank { tile_size, width, height, pixel_indices, palettes }) } - fn perform_load_sound_bank(entry: &AssetEntry, assets_data: Arc>>) -> Result { + fn perform_load_sound_bank( + entry: &AssetEntry, + assets_data: Arc>>, + ) -> Result { if entry.codec != "RAW" { return Err(format!("Unsupported codec: {}", entry.codec)); } let assets_data = assets_data.read().unwrap(); - + let start = entry.offset as usize; let end = start + entry.size as usize; - + if end > assets_data.len() { return Err("Asset offset/size out of bounds".to_string()); } let buffer = &assets_data[start..end]; - let sample_rate = entry.metadata.get("sample_rate").and_then(|v| v.as_u64()).unwrap_or(44100) as u32; - + let sample_rate = + entry.metadata.get("sample_rate").and_then(|v| v.as_u64()).unwrap_or(44100) as u32; + let mut data = Vec::with_capacity(buffer.len() / 2); for i in (0..buffer.len()).step_by(2) { if i + 1 < buffer.len() { - data.push(i16::from_le_bytes([buffer[i], buffer[i+1]])); + data.push(i16::from_le_bytes([buffer[i], buffer[i + 1]])); } } @@ -526,7 +605,7 @@ impl AssetManager { let staging = self.gfx_policy.staging.read().unwrap(); let assets = self.assets.read().unwrap(); let handles = self.handles.read().unwrap(); - + for (handle_id, _) in staging.iter() { if let Some(h) = handles.get(handle_id) { if let Some(entry) = assets.get(&h._asset_id) { @@ -540,7 +619,9 @@ impl AssetManager { { let slots = self.gfx_slots.read().unwrap(); for s in slots.iter() { - if s.is_some() { slots_occupied += 1; } + if s.is_some() { + slots_occupied += 1; + } } } @@ -567,7 +648,7 @@ impl AssetManager { let staging = self.sound_policy.staging.read().unwrap(); let assets = self.assets.read().unwrap(); let handles = self.handles.read().unwrap(); - + for (handle_id, _) in staging.iter() { if let Some(h) = handles.get(handle_id) { if let Some(entry) = assets.get(&h._asset_id) { @@ -581,7 +662,9 @@ impl AssetManager { { let slots = self.sound_slots.read().unwrap(); for s in slots.iter() { - if s.is_some() { slots_occupied += 1; } + if s.is_some() { + slots_occupied += 1; + } } } @@ -602,9 +685,13 @@ impl AssetManager { BankType::TILES => { let slots = self.gfx_slots.read().unwrap(); let asset_id = slots.get(slot.index).and_then(|s| s.clone()); - + let (bytes, asset_name) = if let Some(id) = &asset_id { - let bytes = self.gfx_policy.resident.read().unwrap() + let bytes = self + .gfx_policy + .resident + .read() + .unwrap() .get(id) .map(|entry| entry.bytes) .unwrap_or(0); @@ -614,19 +701,18 @@ impl AssetManager { (0, None) }; - SlotStats { - asset_id, - asset_name, - generation: 0, - resident_bytes: bytes, - } + SlotStats { asset_id, asset_name, generation: 0, resident_bytes: bytes } } BankType::SOUNDS => { let slots = self.sound_slots.read().unwrap(); let asset_id = slots.get(slot.index).and_then(|s| s.clone()); - + let (bytes, asset_name) = if let Some(id) = &asset_id { - let bytes = self.sound_policy.resident.read().unwrap() + let bytes = self + .sound_policy + .resident + .read() + .unwrap() .get(id) .map(|entry| entry.bytes) .unwrap_or(0); @@ -636,12 +722,7 @@ impl AssetManager { (0, None) }; - SlotStats { - asset_id, - asset_name, - generation: 0, - resident_bytes: bytes, - } + SlotStats { asset_id, asset_name, generation: 0, resident_bytes: bytes } } } } @@ -663,7 +744,7 @@ impl AssetManager { } } } - + pub fn shutdown(&self) { self.gfx_policy.clear(); self.sound_policy.clear(); @@ -705,18 +786,18 @@ mod tests { let am = AssetManager::new(vec![asset_entry], data, gfx_installer, sound_installer); let slot = SlotRef::gfx(0); - + let handle = am.load("test_tiles", slot).expect("Should start loading"); - + let mut status = am.status(handle); let start = Instant::now(); while status != LoadStatus::READY && start.elapsed().as_secs() < 5 { thread::sleep(std::time::Duration::from_millis(10)); status = am.status(handle); } - + assert_eq!(status, LoadStatus::READY); - + { let staging = am.gfx_policy.staging.read().unwrap(); assert!(staging.contains_key(&handle)); @@ -724,7 +805,7 @@ mod tests { am.commit(handle); am.apply_commits(); - + assert_eq!(am.status(handle), LoadStatus::COMMITTED); assert!(banks.tile_bank_slot(0).is_some()); } @@ -754,13 +835,13 @@ mod tests { }; let am = AssetManager::new(vec![asset_entry], data, gfx_installer, sound_installer); - + let handle1 = am.load("test_tiles", SlotRef::gfx(0)).unwrap(); let start = Instant::now(); while am.status(handle1) != LoadStatus::READY && start.elapsed().as_secs() < 5 { thread::sleep(std::time::Duration::from_millis(10)); } - + let handle2 = am.load("test_tiles", SlotRef::gfx(1)).unwrap(); assert_eq!(am.status(handle2), LoadStatus::READY); @@ -794,19 +875,19 @@ mod tests { let am = AssetManager::new(vec![asset_entry], data, gfx_installer, sound_installer); let slot = SlotRef::audio(0); - + let handle = am.load("test_sound", slot).expect("Should start loading"); - + let start = Instant::now(); while am.status(handle) != LoadStatus::READY && start.elapsed().as_secs() < 5 { thread::sleep(std::time::Duration::from_millis(10)); } - + assert_eq!(am.status(handle), LoadStatus::READY); - + am.commit(handle); am.apply_commits(); - + assert_eq!(am.status(handle), LoadStatus::COMMITTED); assert!(banks.sound_bank_slot(0).is_some()); } @@ -818,7 +899,7 @@ mod tests { let sound_installer = Arc::clone(&banks) as Arc; let data = vec![0u8; 200]; - + let asset_entry = AssetEntry { asset_id: 2, asset_name: "preload_sound".to_string(), @@ -832,17 +913,15 @@ mod tests { }), }; - let preload = vec![ - PreloadEntry { asset_name: "preload_sound".to_string(), slot: 5 } - ]; + let preload = vec![PreloadEntry { asset_name: "preload_sound".to_string(), slot: 5 }]; let am = AssetManager::new(vec![], vec![], gfx_installer, sound_installer); - + // Before init, slot 5 is empty assert!(banks.sound_bank_slot(5).is_none()); am.initialize_for_cartridge(vec![asset_entry], preload, data); - + // After init, slot 5 should be occupied because of preload assert!(banks.sound_bank_slot(5).is_some()); assert_eq!(am.slot_info(SlotRef::audio(5)).asset_id, Some(2)); @@ -868,9 +947,7 @@ mod tests { metadata: serde_json::json!({ "tile_size": 16, "width": 16, "height": 16 }), }; - let preload = vec![ - PreloadEntry { asset_name: "my_tiles".to_string(), slot: 3 } - ]; + let preload = vec![PreloadEntry { asset_name: "my_tiles".to_string(), slot: 3 }]; let am = AssetManager::new(vec![], vec![], gfx_installer, sound_installer); am.initialize_for_cartridge(vec![asset_entry], preload, data); diff --git a/crates/console/prometeu-drivers/src/audio.rs b/crates/console/prometeu-drivers/src/audio.rs index 77d09ea1..9ab74eb6 100644 --- a/crates/console/prometeu-drivers/src/audio.rs +++ b/crates/console/prometeu-drivers/src/audio.rs @@ -1,18 +1,18 @@ -use std::sync::Arc; use prometeu_hal::AudioBridge; +use std::sync::Arc; /// Maximum number of simultaneous audio voices supported by the hardware. pub const MAX_CHANNELS: usize = 16; /// Standard sample rate for the final audio output. pub const OUTPUT_SAMPLE_RATE: u32 = 48000; +use crate::memory_banks::SoundBankPoolAccess; /// Looping mode for samples (re-exported from the hardware contract). pub use prometeu_hal::LoopMode; use prometeu_hal::sample::Sample; -use crate::memory_banks::SoundBankPoolAccess; /// State of a single playback voice (channel). -/// +/// /// The Core maintains this state to provide information to the App (e.g., is_playing), /// but the actual real-time mixing is performed by the Host using commands. pub struct Channel { @@ -50,7 +50,7 @@ impl Default for Channel { } /// Commands sent from the Core to the Host audio backend. -/// +/// /// Because the Core logic runs at 60Hz and Audio is generated at 48kHz, /// we use an asynchronous command queue to synchronize them. pub enum AudioCommand { @@ -65,24 +65,13 @@ pub enum AudioCommand { loop_mode: LoopMode, }, /// Immediately stop playback on a voice. - Stop { - voice_id: usize, - }, + Stop { voice_id: usize }, /// Update volume of an ongoing playback. - SetVolume { - voice_id: usize, - volume: u8, - }, + SetVolume { voice_id: usize, volume: u8 }, /// Update panning of an ongoing playback. - SetPan { - voice_id: usize, - pan: u8, - }, + SetPan { voice_id: usize, pan: u8 }, /// Update pitch of an ongoing playback. - SetPitch { - voice_id: usize, - pitch: f64, - }, + SetPitch { voice_id: usize, pitch: f64 }, /// Pause all audio processing. MasterPause, /// Resume audio processing. @@ -90,12 +79,12 @@ pub enum AudioCommand { } /// PROMETEU Audio Subsystem. -/// -/// Models a multi-channel PCM sampler (SPU). +/// +/// Models a multi-channel PCM sampler (SPU). /// The audio system in Prometeu is **command-based**. This means the Core -/// doesn't generate raw audio samples; instead, it sends high-level commands -/// (like `Play`, `Stop`, `SetVolume`) to a queue. The physical host backend -/// (e.g., CPAL on desktop) then consumes these commands and performs the +/// doesn't generate raw audio samples; instead, it sends high-level commands +/// (like `Play`, `Stop`, `SetVolume`) to a queue. The physical host backend +/// (e.g., CPAL on desktop) then consumes these commands and performs the /// actual mixing at the native hardware sample rate. /// /// ### Key Features: @@ -103,7 +92,7 @@ pub enum AudioCommand { /// - **Sample-based Synthesis**: Plays PCM data stored in SoundBanks. /// - **Stereo Output**: 48kHz output target. pub struct Audio { - /// Local state of the hardware voices. This state is used for logic + /// Local state of the hardware voices. This state is used for logic /// (e.g., checking if a sound is still playing) and is synchronized with the Host. pub voices: [Channel; MAX_CHANNELS], /// Queue of pending commands to be processed by the Host mixer. @@ -114,20 +103,57 @@ pub struct Audio { } impl AudioBridge for Audio { - fn play(&mut self, bank_id: u8, sample_id: u16, voice_id: usize, volume: u8, pan: u8, pitch: f64, priority: u8, loop_mode: prometeu_hal::LoopMode) { - let lm = match loop_mode { prometeu_hal::LoopMode::Off => LoopMode::Off, prometeu_hal::LoopMode::On => LoopMode::On }; + fn play( + &mut self, + bank_id: u8, + sample_id: u16, + voice_id: usize, + volume: u8, + pan: u8, + pitch: f64, + priority: u8, + loop_mode: prometeu_hal::LoopMode, + ) { + let lm = match loop_mode { + prometeu_hal::LoopMode::Off => LoopMode::Off, + prometeu_hal::LoopMode::On => LoopMode::On, + }; self.play(bank_id, sample_id, voice_id, volume, pan, pitch, priority, lm) } - fn play_sample(&mut self, sample: Arc, voice_id: usize, volume: u8, pan: u8, pitch: f64, priority: u8, loop_mode: prometeu_hal::LoopMode) { - let lm = match loop_mode { prometeu_hal::LoopMode::Off => LoopMode::Off, prometeu_hal::LoopMode::On => LoopMode::On }; + fn play_sample( + &mut self, + sample: Arc, + voice_id: usize, + volume: u8, + pan: u8, + pitch: f64, + priority: u8, + loop_mode: prometeu_hal::LoopMode, + ) { + let lm = match loop_mode { + prometeu_hal::LoopMode::Off => LoopMode::Off, + prometeu_hal::LoopMode::On => LoopMode::On, + }; self.play_sample(sample, voice_id, volume, pan, pitch, priority, lm) } - fn stop(&mut self, voice_id: usize) { self.stop(voice_id) } - fn set_volume(&mut self, voice_id: usize, volume: u8) { self.set_volume(voice_id, volume) } - fn set_pan(&mut self, voice_id: usize, pan: u8) { self.set_pan(voice_id, pan) } - fn set_pitch(&mut self, voice_id: usize, pitch: f64) { self.set_pitch(voice_id, pitch) } - fn is_playing(&self, voice_id: usize) -> bool { self.is_playing(voice_id) } - fn clear_commands(&mut self) { self.clear_commands() } + fn stop(&mut self, voice_id: usize) { + self.stop(voice_id) + } + fn set_volume(&mut self, voice_id: usize, volume: u8) { + self.set_volume(voice_id, volume) + } + fn set_pan(&mut self, voice_id: usize, pan: u8) { + self.set_pan(voice_id, pan) + } + fn set_pitch(&mut self, voice_id: usize, pitch: f64) { + self.set_pitch(voice_id, pitch) + } + fn is_playing(&self, voice_id: usize) -> bool { + self.is_playing(voice_id) + } + fn clear_commands(&mut self) { + self.clear_commands() + } } impl Audio { @@ -140,25 +166,51 @@ impl Audio { } } - pub fn play(&mut self, bank_id: u8, sample_id: u16, voice_id: usize, volume: u8, pan: u8, pitch: f64, priority: u8, loop_mode: LoopMode) { + pub fn play( + &mut self, + bank_id: u8, + sample_id: u16, + voice_id: usize, + volume: u8, + pan: u8, + pitch: f64, + priority: u8, + loop_mode: LoopMode, + ) { if voice_id >= MAX_CHANNELS { return; } // Resolve the sample from the hardware pools - let sample = self.sound_banks.sound_bank_slot(bank_id as usize) + let sample = self + .sound_banks + .sound_bank_slot(bank_id as usize) .and_then(|bank| bank.samples.get(sample_id as usize).map(Arc::clone)); - if let Some(s) = sample { - println!("[Audio] Resolved sample from bank {} sample {}. Playing on voice {}.", bank_id, sample_id, voice_id); + println!( + "[Audio] Resolved sample from bank {} sample {}. Playing on voice {}.", + bank_id, sample_id, voice_id + ); self.play_sample(s, voice_id, volume, pan, pitch, priority, loop_mode); } else { - eprintln!("[Audio] Failed to resolve sample from bank {} sample {}.", bank_id, sample_id); + eprintln!( + "[Audio] Failed to resolve sample from bank {} sample {}.", + bank_id, sample_id + ); } } - pub fn play_sample(&mut self, sample: Arc, voice_id: usize, volume: u8, pan: u8, pitch: f64, priority: u8, loop_mode: LoopMode) { + pub fn play_sample( + &mut self, + sample: Arc, + voice_id: usize, + volume: u8, + pan: u8, + pitch: f64, + priority: u8, + loop_mode: LoopMode, + ) { if voice_id >= MAX_CHANNELS { return; } @@ -217,11 +269,7 @@ impl Audio { } pub fn is_playing(&self, voice_id: usize) -> bool { - if voice_id < MAX_CHANNELS { - self.voices[voice_id].active - } else { - false - } + if voice_id < MAX_CHANNELS { self.voices[voice_id].active } else { false } } /// Clears the command queue. The Host should consume this every frame. diff --git a/crates/console/prometeu-drivers/src/gfx.rs b/crates/console/prometeu-drivers/src/gfx.rs index 5e36e3de..16f5802d 100644 --- a/crates/console/prometeu-drivers/src/gfx.rs +++ b/crates/console/prometeu-drivers/src/gfx.rs @@ -1,15 +1,15 @@ use crate::memory_banks::TileBankPoolAccess; -use std::sync::Arc; -use prometeu_hal::color::Color; use prometeu_hal::GfxBridge; +use prometeu_hal::color::Color; use prometeu_hal::sprite::Sprite; use prometeu_hal::tile::Tile; use prometeu_hal::tile_bank::{TileBank, TileSize}; use prometeu_hal::tile_layer::{HudTileLayer, ScrollableTileLayer, TileMap}; +use std::sync::Arc; /// Blending modes inspired by classic 16-bit hardware. /// Defines how source pixels are combined with existing pixels in the framebuffer. -/// +/// /// ### Usage Example: /// // Draw a semi-transparent blue rectangle /// gfx.fill_rect_blend(10, 10, 50, 50, Color::BLUE, BlendMode::Half); @@ -31,10 +31,10 @@ pub enum BlendMode { /// PROMETEU Graphics Subsystem (GFX). /// /// Models a specialized graphics chip with a fixed resolution, double buffering, -/// and a multi-layered tile/sprite architecture. -/// -/// The GFX system works by composing several "layers" into a single 16-bit -/// RGB565 framebuffer. It supports hardware-accelerated primitives (lines, rects) +/// and a multi-layered tile/sprite architecture. +/// +/// The GFX system works by composing several "layers" into a single 16-bit +/// RGB565 framebuffer. It supports hardware-accelerated primitives (lines, rects) /// and specialized console features like background scrolling and sprite sorting. /// /// ### Layer Composition Order (back to front): @@ -79,44 +79,139 @@ pub struct Gfx { } impl GfxBridge for Gfx { - fn size(&self) -> (usize, usize) { self.size() } - fn front_buffer(&self) -> &[u16] { self.front_buffer() } - fn clear(&mut self, color: Color) { self.clear(color) } - fn fill_rect_blend(&mut self, x: i32, y: i32, w: i32, h: i32, color: Color, mode: prometeu_hal::BlendMode) { let m = match mode { prometeu_hal::BlendMode::None => BlendMode::None, prometeu_hal::BlendMode::Half => BlendMode::Half, prometeu_hal::BlendMode::HalfPlus => BlendMode::HalfPlus, prometeu_hal::BlendMode::HalfMinus => BlendMode::HalfMinus, prometeu_hal::BlendMode::Full => BlendMode::Full }; self.fill_rect_blend(x, y, w, h, color, m) } - fn fill_rect(&mut self, x: i32, y: i32, w: i32, h: i32, color: Color) { self.fill_rect(x, y, w, h, color) } - fn draw_pixel(&mut self, x: i32, y: i32, color: Color) { self.draw_pixel(x, y, color) } - fn draw_line(&mut self, x0: i32, y0: i32, x1: i32, y1: i32, color: Color) { self.draw_line(x0, y0, x1, y1, color) } - fn draw_circle(&mut self, xc: i32, yc: i32, r: i32, color: Color) { self.draw_circle(xc, yc, r, color) } - fn draw_circle_points(&mut self, xc: i32, yc: i32, x: i32, y: i32, color: Color) { self.draw_circle_points(xc, yc, x, y, color) } - fn fill_circle(&mut self, xc: i32, yc: i32, r: i32, color: Color) { self.fill_circle(xc, yc, r, color) } - fn draw_circle_lines(&mut self, xc: i32, yc: i32, x: i32, y: i32, color: Color) { self.draw_circle_lines(xc, yc, x, y, color) } - fn draw_disc(&mut self, x: i32, y: i32, r: i32, border_color: Color, fill_color: Color) { self.draw_disc(x, y, r, border_color, fill_color) } - fn draw_rect(&mut self, x: i32, y: i32, w: i32, h: i32, color: Color) { self.draw_rect(x, y, w, h, color) } - fn draw_square(&mut self, x: i32, y: i32, w: i32, h: i32, border_color: Color, fill_color: Color) { self.draw_square(x, y, w, h, border_color, fill_color) } - fn draw_horizontal_line(&mut self, x0: i32, x1: i32, y: i32, color: Color) { self.draw_horizontal_line(x0, x1, y, color) } - fn draw_vertical_line(&mut self, x: i32, y0: i32, y1: i32, color: Color) { self.draw_vertical_line(x, y0, y1, color) } - fn present(&mut self) { self.present() } - fn render_all(&mut self) { self.render_all() } - fn render_layer(&mut self, layer_idx: usize) { self.render_layer(layer_idx) } - fn render_hud(&mut self) { self.render_hud() } - fn draw_text(&mut self, x: i32, y: i32, text: &str, color: Color) { self.draw_text(x, y, text, color) } - fn draw_char(&mut self, x: i32, y: i32, c: char, color: Color) { self.draw_char(x, y, c, color) } + fn size(&self) -> (usize, usize) { + self.size() + } + fn front_buffer(&self) -> &[u16] { + self.front_buffer() + } + fn clear(&mut self, color: Color) { + self.clear(color) + } + fn fill_rect_blend( + &mut self, + x: i32, + y: i32, + w: i32, + h: i32, + color: Color, + mode: prometeu_hal::BlendMode, + ) { + let m = match mode { + prometeu_hal::BlendMode::None => BlendMode::None, + prometeu_hal::BlendMode::Half => BlendMode::Half, + prometeu_hal::BlendMode::HalfPlus => BlendMode::HalfPlus, + prometeu_hal::BlendMode::HalfMinus => BlendMode::HalfMinus, + prometeu_hal::BlendMode::Full => BlendMode::Full, + }; + self.fill_rect_blend(x, y, w, h, color, m) + } + fn fill_rect(&mut self, x: i32, y: i32, w: i32, h: i32, color: Color) { + self.fill_rect(x, y, w, h, color) + } + fn draw_pixel(&mut self, x: i32, y: i32, color: Color) { + self.draw_pixel(x, y, color) + } + fn draw_line(&mut self, x0: i32, y0: i32, x1: i32, y1: i32, color: Color) { + self.draw_line(x0, y0, x1, y1, color) + } + fn draw_circle(&mut self, xc: i32, yc: i32, r: i32, color: Color) { + self.draw_circle(xc, yc, r, color) + } + fn draw_circle_points(&mut self, xc: i32, yc: i32, x: i32, y: i32, color: Color) { + self.draw_circle_points(xc, yc, x, y, color) + } + fn fill_circle(&mut self, xc: i32, yc: i32, r: i32, color: Color) { + self.fill_circle(xc, yc, r, color) + } + fn draw_circle_lines(&mut self, xc: i32, yc: i32, x: i32, y: i32, color: Color) { + self.draw_circle_lines(xc, yc, x, y, color) + } + fn draw_disc(&mut self, x: i32, y: i32, r: i32, border_color: Color, fill_color: Color) { + self.draw_disc(x, y, r, border_color, fill_color) + } + fn draw_rect(&mut self, x: i32, y: i32, w: i32, h: i32, color: Color) { + self.draw_rect(x, y, w, h, color) + } + fn draw_square( + &mut self, + x: i32, + y: i32, + w: i32, + h: i32, + border_color: Color, + fill_color: Color, + ) { + self.draw_square(x, y, w, h, border_color, fill_color) + } + fn draw_horizontal_line(&mut self, x0: i32, x1: i32, y: i32, color: Color) { + self.draw_horizontal_line(x0, x1, y, color) + } + fn draw_vertical_line(&mut self, x: i32, y0: i32, y1: i32, color: Color) { + self.draw_vertical_line(x, y0, y1, color) + } + fn present(&mut self) { + self.present() + } + fn render_all(&mut self) { + self.render_all() + } + fn render_layer(&mut self, layer_idx: usize) { + self.render_layer(layer_idx) + } + fn render_hud(&mut self) { + self.render_hud() + } + fn draw_text(&mut self, x: i32, y: i32, text: &str, color: Color) { + self.draw_text(x, y, text, color) + } + fn draw_char(&mut self, x: i32, y: i32, c: char, color: Color) { + self.draw_char(x, y, c, color) + } - fn layer(&self, index: usize) -> &ScrollableTileLayer { &self.layers[index] } - fn layer_mut(&mut self, index: usize) -> &mut ScrollableTileLayer { &mut self.layers[index] } - fn hud(&self) -> &HudTileLayer { &self.hud } - fn hud_mut(&mut self) -> &mut HudTileLayer { &mut self.hud } - fn sprite(&self, index: usize) -> &Sprite { &self.sprites[index] } - fn sprite_mut(&mut self, index: usize) -> &mut Sprite { &mut self.sprites[index] } + fn layer(&self, index: usize) -> &ScrollableTileLayer { + &self.layers[index] + } + fn layer_mut(&mut self, index: usize) -> &mut ScrollableTileLayer { + &mut self.layers[index] + } + fn hud(&self) -> &HudTileLayer { + &self.hud + } + fn hud_mut(&mut self) -> &mut HudTileLayer { + &mut self.hud + } + fn sprite(&self, index: usize) -> &Sprite { + &self.sprites[index] + } + fn sprite_mut(&mut self, index: usize) -> &mut Sprite { + &mut self.sprites[index] + } - fn scene_fade_level(&self) -> u8 { self.scene_fade_level } - fn set_scene_fade_level(&mut self, level: u8) { self.scene_fade_level = level; } - fn scene_fade_color(&self) -> Color { self.scene_fade_color } - fn set_scene_fade_color(&mut self, color: Color) { self.scene_fade_color = color; } - fn hud_fade_level(&self) -> u8 { self.hud_fade_level } - fn set_hud_fade_level(&mut self, level: u8) { self.hud_fade_level = level; } - fn hud_fade_color(&self) -> Color { self.hud_fade_color } - fn set_hud_fade_color(&mut self, color: Color) { self.hud_fade_color = color; } + fn scene_fade_level(&self) -> u8 { + self.scene_fade_level + } + fn set_scene_fade_level(&mut self, level: u8) { + self.scene_fade_level = level; + } + fn scene_fade_color(&self) -> Color { + self.scene_fade_color + } + fn set_scene_fade_color(&mut self, color: Color) { + self.scene_fade_color = color; + } + fn hud_fade_level(&self) -> u8 { + self.hud_fade_level + } + fn set_hud_fade_level(&mut self, level: u8) { + self.hud_fade_level = level; + } + fn hud_fade_color(&self) -> Color { + self.hud_fade_color + } + fn set_hud_fade_color(&mut self, color: Color) { + self.hud_fade_color = color; + } } impl Gfx { @@ -187,7 +282,9 @@ impl Gfx { color: Color, mode: BlendMode, ) { - if color == Color::COLOR_KEY { return; } + if color == Color::COLOR_KEY { + return; + } let fw = self.w as i32; let fh = self.h as i32; @@ -216,7 +313,9 @@ impl Gfx { /// Draws a single pixel. pub fn draw_pixel(&mut self, x: i32, y: i32, color: Color) { - if color == Color::COLOR_KEY { return; } + if color == Color::COLOR_KEY { + return; + } if x >= 0 && x < self.w as i32 && y >= 0 && y < self.h as i32 { self.back[y as usize * self.w + x as usize] = color.0; } @@ -224,7 +323,9 @@ impl Gfx { /// Draws a line between two points using Bresenham's algorithm. pub fn draw_line(&mut self, x0: i32, y0: i32, x1: i32, y1: i32, color: Color) { - if color == Color::COLOR_KEY { return; } + if color == Color::COLOR_KEY { + return; + } let dx = (x1 - x0).abs(); let sx = if x0 < x1 { 1 } else { -1 }; @@ -237,7 +338,9 @@ impl Gfx { loop { self.draw_pixel(x, y, color); - if x == x1 && y == y1 { break; } + if x == x1 && y == y1 { + break; + } let e2 = 2 * err; if e2 >= dy { err += dy; @@ -252,9 +355,13 @@ impl Gfx { /// Draws a circle outline using Midpoint Circle Algorithm. pub fn draw_circle(&mut self, xc: i32, yc: i32, r: i32, color: Color) { - if color == Color::COLOR_KEY { return; } + if color == Color::COLOR_KEY { + return; + } - if r < 0 { return; } + if r < 0 { + return; + } let mut x = 0; let mut y = r; let mut d = 3 - 2 * r; @@ -284,9 +391,13 @@ impl Gfx { /// Draws a filled circle. pub fn fill_circle(&mut self, xc: i32, yc: i32, r: i32, color: Color) { - if color == Color::COLOR_KEY { return; } + if color == Color::COLOR_KEY { + return; + } - if r < 0 { return; } + if r < 0 { + return; + } let mut x = 0; let mut y = r; let mut d = 3 - 2 * r; @@ -318,9 +429,13 @@ impl Gfx { /// Draws a rectangle outline. pub fn draw_rect(&mut self, x: i32, y: i32, w: i32, h: i32, color: Color) { - if color == Color::COLOR_KEY { return; } + if color == Color::COLOR_KEY { + return; + } - if w <= 0 || h <= 0 { return; } + if w <= 0 || h <= 0 { + return; + } self.draw_horizontal_line(x, x + w - 1, y, color); self.draw_horizontal_line(x, x + w - 1, y + h - 1, color); self.draw_vertical_line(x, y, y + h - 1, color); @@ -328,30 +443,50 @@ impl Gfx { } /// Draws a square (filled rectangle with border). - pub fn draw_square(&mut self, x: i32, y: i32, w: i32, h: i32, border_color: Color, fill_color: Color) { + pub fn draw_square( + &mut self, + x: i32, + y: i32, + w: i32, + h: i32, + border_color: Color, + fill_color: Color, + ) { self.fill_rect(x, y, w, h, fill_color); self.draw_rect(x, y, w, h, border_color); } fn draw_horizontal_line(&mut self, x0: i32, x1: i32, y: i32, color: Color) { - if color == Color::COLOR_KEY { return; } + if color == Color::COLOR_KEY { + return; + } - if y < 0 || y >= self.h as i32 { return; } + if y < 0 || y >= self.h as i32 { + return; + } let start = x0.max(0); let end = x1.min(self.w as i32 - 1); - if start > end { return; } + if start > end { + return; + } for x in start..=end { self.back[y as usize * self.w + x as usize] = color.0; } } fn draw_vertical_line(&mut self, x: i32, y0: i32, y1: i32, color: Color) { - if color == Color::COLOR_KEY { return; } + if color == Color::COLOR_KEY { + return; + } - if x < 0 || x >= self.w as i32 { return; } + if x < 0 || x >= self.w as i32 { + return; + } let start = y0.max(0); let end = y1.min(self.h as i32 - 1); - if start > end { return; } + if start > end { + return; + } for y in start..=end { self.back[y as usize * self.w + x as usize] = color.0; } @@ -365,7 +500,7 @@ impl Gfx { /// The main rendering pipeline. /// - /// This method composes the final frame by rasterizing layers and sprites in the + /// This method composes the final frame by rasterizing layers and sprites in the /// correct priority order into the back buffer. /// Follows the hardware model where layers and sprites are composed every frame. pub fn render_all(&mut self) { @@ -382,18 +517,40 @@ impl Gfx { } // 1. Priority 0 sprites: drawn at the very back, behind everything else. - Self::draw_bucket_on_buffer(&mut self.back, self.w, self.h, &self.priority_buckets[0], &self.sprites, &*self.tile_banks); + Self::draw_bucket_on_buffer( + &mut self.back, + self.w, + self.h, + &self.priority_buckets[0], + &self.sprites, + &*self.tile_banks, + ); // 2. Main layers and prioritized sprites. // Order: Layer 0 -> Sprites 1 -> Layer 1 -> Sprites 2 ... for i in 0..self.layers.len() { let bank_id = self.layers[i].bank_id as usize; if let Some(bank) = self.tile_banks.tile_bank_slot(bank_id) { - Self::draw_tile_map(&mut self.back, self.w, self.h, &self.layers[i].map, &bank, self.layers[i].scroll_x, self.layers[i].scroll_y); + Self::draw_tile_map( + &mut self.back, + self.w, + self.h, + &self.layers[i].map, + &bank, + self.layers[i].scroll_x, + self.layers[i].scroll_y, + ); } // Draw sprites that belong to this depth level - Self::draw_bucket_on_buffer(&mut self.back, self.w, self.h, &self.priority_buckets[i + 1], &self.sprites, &*self.tile_banks); + Self::draw_bucket_on_buffer( + &mut self.back, + self.w, + self.h, + &self.priority_buckets[i + 1], + &self.sprites, + &*self.tile_banks, + ); } // 4. Scene Fade: Applies a color blend to the entire world (excluding HUD). @@ -408,7 +565,9 @@ impl Gfx { /// Renders a specific game layer. pub fn render_layer(&mut self, layer_idx: usize) { - if layer_idx >= self.layers.len() { return; } + if layer_idx >= self.layers.len() { + return; + } let bank_id = self.layers[layer_idx].bank_id as usize; let scroll_x = self.layers[layer_idx].scroll_x; @@ -419,7 +578,15 @@ impl Gfx { _ => return, }; - Self::draw_tile_map(&mut self.back, self.w, self.h, &self.layers[layer_idx].map, &bank, scroll_x, scroll_y); + Self::draw_tile_map( + &mut self.back, + self.w, + self.h, + &self.layers[layer_idx].map, + &bank, + scroll_x, + scroll_y, + ); } /// Renders the HUD (fixed position, no scroll). @@ -427,7 +594,13 @@ impl Gfx { Self::render_hud_with_pool(&mut self.back, self.w, self.h, &self.hud, &*self.tile_banks); } - fn render_hud_with_pool(back: &mut [u16], w: usize, h: usize, hud: &HudTileLayer, tile_banks: &dyn TileBankPoolAccess) { + fn render_hud_with_pool( + back: &mut [u16], + w: usize, + h: usize, + hud: &HudTileLayer, + tile_banks: &dyn TileBankPoolAccess, + ) { let bank_id = hud.bank_id as usize; let bank = match tile_banks.tile_bank_slot(bank_id) { Some(b) => b, @@ -445,7 +618,7 @@ impl Gfx { map: &TileMap, bank: &TileBank, scroll_x: i32, - scroll_y: i32 + scroll_y: i32, ) { let tile_size = bank.tile_size as usize; @@ -469,34 +642,58 @@ impl Gfx { let map_y = (start_tile_y + ty as i32) as usize; // Bounds check: don't draw if the camera is outside the map. - if map_x >= map.width || map_y >= map.height { continue; } + if map_x >= map.width || map_y >= map.height { + continue; + } let tile = map.tiles[map_y * map.width + map_x]; - + // Optimized skip for empty (ID 0) tiles. - if tile.id == 0 { continue; } + if tile.id == 0 { + continue; + } // 5. Project the tile pixels to screen space. let screen_tile_x = (tx as i32 * tile_size as i32) - fine_scroll_x; let screen_tile_y = (ty as i32 * tile_size as i32) - fine_scroll_y; - Self::draw_tile_pixels(back, screen_w, screen_h, screen_tile_x, screen_tile_y, tile, bank); + Self::draw_tile_pixels( + back, + screen_w, + screen_h, + screen_tile_x, + screen_tile_y, + tile, + bank, + ); } } } /// Internal helper to copy a single tile's pixels to the framebuffer. /// Handles flipping and palette resolution. - fn draw_tile_pixels(back: &mut [u16], screen_w: usize, screen_h: usize, x: i32, y: i32, tile: Tile, bank: &TileBank) { + fn draw_tile_pixels( + back: &mut [u16], + screen_w: usize, + screen_h: usize, + x: i32, + y: i32, + tile: Tile, + bank: &TileBank, + ) { let size = bank.tile_size as usize; for local_y in 0..size { let world_y = y + local_y as i32; - if world_y < 0 || world_y >= screen_h as i32 { continue; } + if world_y < 0 || world_y >= screen_h as i32 { + continue; + } for local_x in 0..size { let world_x = x + local_x as i32; - if world_x < 0 || world_x >= screen_w as i32 { continue; } + if world_x < 0 || world_x >= screen_w as i32 { + continue; + } // Handle flip flags by reversing the fetch coordinates. let fetch_x = if tile.flip_x { size - 1 - local_x } else { local_x }; @@ -506,7 +703,9 @@ impl Gfx { let px_index = bank.get_pixel_index(tile.id, fetch_x, fetch_y); // 2. Hardware rule: Color index 0 is always fully transparent. - if px_index == 0 { continue; } + if px_index == 0 { + continue; + } // 3. Resolve the virtual index to a real RGB565 color using the tile's assigned palette. let color = bank.resolve_color(tile.palette_id, px_index); @@ -538,7 +737,7 @@ impl Gfx { screen_w: usize, screen_h: usize, sprite: &Sprite, - bank: &TileBank + bank: &TileBank, ) { // ... (same bounds/clipping calculation we already had) ... let size = bank.tile_size as usize; @@ -559,7 +758,9 @@ impl Gfx { let px_index = bank.get_pixel_index(sprite.tile.id, fetch_x, fetch_y); // 2. Transparency - if px_index == 0 { continue; } + if px_index == 0 { + continue; + } // 3. Resolve color via palette (from the tile inside the sprite) let color = bank.resolve_color(sprite.tile.palette_id, px_index); @@ -572,7 +773,9 @@ impl Gfx { /// Applies the fade effect to the entire back buffer. /// level: 0 (full color) to 31 (visible) fn apply_fade_to_buffer(back: &mut [u16], level: u8, fade_color: Color) { - if level >= 31 { return; } // Fully visible, skip processing + if level >= 31 { + return; + } // Fully visible, skip processing let weight = level as u16; let inv_weight = 31 - weight; @@ -674,7 +877,7 @@ mod tests { let mut gfx = Gfx::new(10, 10, banks); gfx.draw_pixel(5, 5, Color::WHITE); assert_eq!(gfx.back[5 * 10 + 5], Color::WHITE.0); - + // Out of bounds should not panic gfx.draw_pixel(-1, -1, Color::WHITE); gfx.draw_pixel(10, 10, Color::WHITE); diff --git a/crates/console/prometeu-drivers/src/hardware.rs b/crates/console/prometeu-drivers/src/hardware.rs index fcd92b82..abaa72e1 100644 --- a/crates/console/prometeu-drivers/src/hardware.rs +++ b/crates/console/prometeu-drivers/src/hardware.rs @@ -1,17 +1,20 @@ -use std::sync::Arc; -use prometeu_hal::{AssetBridge, AudioBridge, GfxBridge, HardwareBridge, PadBridge, TouchBridge}; use crate::asset::AssetManager; use crate::audio::Audio; use crate::gfx::Gfx; -use crate::memory_banks::{MemoryBanks, SoundBankPoolAccess, SoundBankPoolInstaller, TileBankPoolAccess, TileBankPoolInstaller}; +use crate::memory_banks::{ + MemoryBanks, SoundBankPoolAccess, SoundBankPoolInstaller, TileBankPoolAccess, + TileBankPoolInstaller, +}; use crate::pad::Pad; use crate::touch::Touch; +use prometeu_hal::{AssetBridge, AudioBridge, GfxBridge, HardwareBridge, PadBridge, TouchBridge}; +use std::sync::Arc; /// Aggregate structure for all virtual hardware peripherals. -/// +/// /// This struct represents the "Mainboard" of the PROMETEU console. -/// It acts as a container for all hardware subsystems. In the Prometeu -/// architecture, hardware is decoupled from the OS and VM, allowing +/// It acts as a container for all hardware subsystems. In the Prometeu +/// architecture, hardware is decoupled from the OS and VM, allowing /// for easier testing and different host implementations (Desktop, Web, etc.). /// /// ### Console Specifications: @@ -33,20 +36,40 @@ pub struct Hardware { } impl HardwareBridge for Hardware { - fn gfx(&self) -> &dyn GfxBridge { &self.gfx } - fn gfx_mut(&mut self) -> &mut dyn GfxBridge { &mut self.gfx } + fn gfx(&self) -> &dyn GfxBridge { + &self.gfx + } + fn gfx_mut(&mut self) -> &mut dyn GfxBridge { + &mut self.gfx + } - fn audio(&self) -> &dyn AudioBridge { &self.audio } - fn audio_mut(&mut self) -> &mut dyn AudioBridge { &mut self.audio } + fn audio(&self) -> &dyn AudioBridge { + &self.audio + } + fn audio_mut(&mut self) -> &mut dyn AudioBridge { + &mut self.audio + } - fn pad(&self) -> &dyn PadBridge { &self.pad } - fn pad_mut(&mut self) -> &mut dyn PadBridge { &mut self.pad } + fn pad(&self) -> &dyn PadBridge { + &self.pad + } + fn pad_mut(&mut self) -> &mut dyn PadBridge { + &mut self.pad + } - fn touch(&self) -> &dyn TouchBridge { &self.touch } - fn touch_mut(&mut self) -> &mut dyn TouchBridge { &mut self.touch } + fn touch(&self) -> &dyn TouchBridge { + &self.touch + } + fn touch_mut(&mut self) -> &mut dyn TouchBridge { + &mut self.touch + } - fn assets(&self) -> &dyn AssetBridge { &self.assets } - fn assets_mut(&mut self) -> &mut dyn AssetBridge { &mut self.assets } + fn assets(&self) -> &dyn AssetBridge { + &self.assets + } + fn assets_mut(&mut self) -> &mut dyn AssetBridge { + &mut self.assets + } } impl Hardware { @@ -59,7 +82,11 @@ impl Hardware { pub fn new() -> Self { let memory_banks = Arc::new(MemoryBanks::new()); Self { - gfx: Gfx::new(Self::W, Self::H, Arc::clone(&memory_banks) as Arc), + gfx: Gfx::new( + Self::W, + Self::H, + Arc::clone(&memory_banks) as Arc, + ), audio: Audio::new(Arc::clone(&memory_banks) as Arc), pad: Pad::default(), touch: Touch::default(), diff --git a/crates/console/prometeu-drivers/src/lib.rs b/crates/console/prometeu-drivers/src/lib.rs index deb14174..eea0e5fc 100644 --- a/crates/console/prometeu-drivers/src/lib.rs +++ b/crates/console/prometeu-drivers/src/lib.rs @@ -1,13 +1,13 @@ mod asset; +mod audio; mod gfx; +pub mod hardware; +mod memory_banks; mod pad; mod touch; -mod audio; -mod memory_banks; -pub mod hardware; pub use crate::asset::AssetManager; pub use crate::audio::{Audio, AudioCommand, Channel, MAX_CHANNELS, OUTPUT_SAMPLE_RATE}; pub use crate::gfx::Gfx; pub use crate::memory_banks::MemoryBanks; -pub use crate::pad::Pad; \ No newline at end of file +pub use crate::pad::Pad; diff --git a/crates/console/prometeu-drivers/src/memory_banks.rs b/crates/console/prometeu-drivers/src/memory_banks.rs index 61482bb8..4293fe6f 100644 --- a/crates/console/prometeu-drivers/src/memory_banks.rs +++ b/crates/console/prometeu-drivers/src/memory_banks.rs @@ -1,6 +1,6 @@ -use std::sync::{Arc, RwLock}; use prometeu_hal::sound_bank::SoundBank; use prometeu_hal::tile_bank::TileBank; +use std::sync::{Arc, RwLock}; /// Non-generic interface for peripherals to access graphical tile banks. pub trait TileBankPoolAccess: Send + Sync { @@ -31,7 +31,7 @@ pub trait SoundBankPoolInstaller: Send + Sync { } /// Centralized container for all hardware memory banks. -/// +/// /// MemoryBanks represent the actual hardware slot state. /// Peripherals consume this state via narrow, non-generic traits. /// AssetManager coordinates residency and installs assets into these slots. diff --git a/crates/console/prometeu-drivers/src/pad.rs b/crates/console/prometeu-drivers/src/pad.rs index da1739e0..ec08dd84 100644 --- a/crates/console/prometeu-drivers/src/pad.rs +++ b/crates/console/prometeu-drivers/src/pad.rs @@ -1,5 +1,5 @@ -use prometeu_hal::{InputSignals, PadBridge}; use prometeu_hal::button::Button; +use prometeu_hal::{InputSignals, PadBridge}; #[derive(Default, Clone, Copy, Debug)] pub struct Pad { @@ -10,31 +10,59 @@ pub struct Pad { pub a: Button, // ps: square pub b: Button, // ps: circle - pub x: Button, // ps: triangle + pub x: Button, // ps: triangle pub y: Button, // ps: cross pub l: Button, // ps: R pub r: Button, // ps: L pub start: Button, - pub select: Button, + pub select: Button, } impl PadBridge for Pad { - fn begin_frame(&mut self, signals: &InputSignals) { self.begin_frame(signals) } - fn any(&self) -> bool { self.any() } + fn begin_frame(&mut self, signals: &InputSignals) { + self.begin_frame(signals) + } + fn any(&self) -> bool { + self.any() + } - fn up(&self) -> &Button { &self.up } - fn down(&self) -> &Button { &self.down } - fn left(&self) -> &Button { &self.left } - fn right(&self) -> &Button { &self.right } - fn a(&self) -> &Button { &self.a } - fn b(&self) -> &Button { &self.b } - fn x(&self) -> &Button { &self.x } - fn y(&self) -> &Button { &self.y } - fn l(&self) -> &Button { &self.l } - fn r(&self) -> &Button { &self.r } - fn start(&self) -> &Button { &self.start } - fn select(&self) -> &Button { &self.select } + fn up(&self) -> &Button { + &self.up + } + fn down(&self) -> &Button { + &self.down + } + fn left(&self) -> &Button { + &self.left + } + fn right(&self) -> &Button { + &self.right + } + fn a(&self) -> &Button { + &self.a + } + fn b(&self) -> &Button { + &self.b + } + fn x(&self) -> &Button { + &self.x + } + fn y(&self) -> &Button { + &self.y + } + fn l(&self) -> &Button { + &self.l + } + fn r(&self) -> &Button { + &self.r + } + fn start(&self) -> &Button { + &self.start + } + fn select(&self) -> &Button { + &self.select + } } impl Pad { @@ -43,18 +71,18 @@ impl Pad { self.down.begin_frame(signals.down_signal); self.left.begin_frame(signals.left_signal); self.right.begin_frame(signals.right_signal); - + self.a.begin_frame(signals.a_signal); self.b.begin_frame(signals.b_signal); self.x.begin_frame(signals.x_signal); self.y.begin_frame(signals.y_signal); self.l.begin_frame(signals.l_signal); self.r.begin_frame(signals.r_signal); - + self.start.begin_frame(signals.start_signal); - self.select.begin_frame(signals.select_signal); + self.select.begin_frame(signals.select_signal); } - + pub fn any(&self) -> bool { self.a.down || self.b.down @@ -66,5 +94,3 @@ impl Pad { || self.select.down } } - - diff --git a/crates/console/prometeu-drivers/src/touch.rs b/crates/console/prometeu-drivers/src/touch.rs index fcdbcc5c..346afedb 100644 --- a/crates/console/prometeu-drivers/src/touch.rs +++ b/crates/console/prometeu-drivers/src/touch.rs @@ -1,5 +1,5 @@ -use prometeu_hal::{InputSignals, TouchBridge}; use prometeu_hal::button::Button; +use prometeu_hal::{InputSignals, TouchBridge}; #[derive(Default, Clone, Copy, Debug)] pub struct Touch { @@ -18,8 +18,16 @@ impl Touch { } impl TouchBridge for Touch { - fn begin_frame(&mut self, signals: &InputSignals) { self.begin_frame(signals) } - fn f(&self) -> &Button { &self.f } - fn x(&self) -> i32 { self.x } - fn y(&self) -> i32 { self.y } -} \ No newline at end of file + fn begin_frame(&mut self, signals: &InputSignals) { + self.begin_frame(signals) + } + fn f(&self) -> &Button { + &self.f + } + fn x(&self) -> i32 { + self.x + } + fn y(&self) -> i32 { + self.y + } +} diff --git a/crates/console/prometeu-drivers/tests/heartbeat.rs b/crates/console/prometeu-drivers/tests/heartbeat.rs index e56e95a9..33a9d9ee 100644 --- a/crates/console/prometeu-drivers/tests/heartbeat.rs +++ b/crates/console/prometeu-drivers/tests/heartbeat.rs @@ -4,7 +4,7 @@ // use prometeu_drivers::hardware::Hardware; // use prometeu_hal::{HostContext, HostReturn, NativeInterface}; // use prometeu_vm::{LogicalFrameEndingReason, VirtualMachine}; -// +// // struct MockNative; // impl NativeInterface for MockNative { // fn syscall(&mut self, id: u32, _args: &[Value], ret: &mut HostReturn, _ctx: &mut HostContext) -> Result<(), VmFault> { @@ -26,30 +26,30 @@ // Ok(()) // } // } -// +// // #[test] // fn test_canonical_cartridge_heartbeat() { // let mut pbc_path = Path::new("../../test-cartridges/canonical/golden/program.pbc").to_path_buf(); // if !pbc_path.exists() { // pbc_path = Path::new("test-cartridges/canonical/golden/program.pbc").to_path_buf(); // } -// +// // let pbc_bytes = fs::read(pbc_path).expect("Failed to read canonical PBC. Did you run the generation test?"); -// +// // // Determine entrypoint from the compiled module exports // let entry_symbol = "src/main/modules:frame"; -// +// // let mut vm = VirtualMachine::new(vec![], vec![]); // vm.initialize(pbc_bytes, entry_symbol).expect("Failed to initialize VM with canonical cartridge"); // vm.prepare_call(entry_symbol); -// +// // let mut native = MockNative; // let mut hw = Hardware::new(); // let mut ctx = HostContext::new(Some(&mut hw)); -// +// // // Run for a reasonable budget // let report = vm.run_budget(1000, &mut native, &mut ctx).expect("VM failed to run"); -// +// // // Acceptance criteria: // // 1. No traps // match report.reason { @@ -61,7 +61,7 @@ // LogicalFrameEndingReason::BudgetExhausted => {}, // LogicalFrameEndingReason::Breakpoint => {}, // } -// +// // // 2. Deterministic output state (if any) // // Observaรงรฃo: a PVM agora sinaliza FRAME_SYNC imediatamente antes do RET do entry point. // // Quando o motivo รฉ FRAME_SYNC, nรฃo exigimos pilha vazia (a limpeza final ocorre apรณs o RET). @@ -69,6 +69,6 @@ // if !matches!(report.reason, LogicalFrameEndingReason::FrameSync) { // assert_eq!(vm.operand_stack.len(), 0, "Stack should be empty after frame() execution"); // } -// +// // println!("Heartbeat test passed!"); // } diff --git a/crates/console/prometeu-firmware/src/firmware/firmware.rs b/crates/console/prometeu-firmware/src/firmware/firmware.rs index 2c8bfd29..248c64f2 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware.rs @@ -1,23 +1,23 @@ -use prometeu_hal::telemetry::CertificationConfig; -use prometeu_hal::{HardwareBridge, InputSignals}; -use prometeu_hal::cartridge::Cartridge; -use prometeu_system::{PrometeuHub, VirtualMachineRuntime}; -use prometeu_vm::VirtualMachine; use crate::firmware::boot_target::BootTarget; use crate::firmware::firmware_state::{FirmwareState, LoadCartridgeStep, ResetStep}; use crate::firmware::prometeu_context::PrometeuContext; +use prometeu_hal::cartridge::Cartridge; +use prometeu_hal::telemetry::CertificationConfig; +use prometeu_hal::{HardwareBridge, InputSignals}; +use prometeu_system::{PrometeuHub, VirtualMachineRuntime}; +use prometeu_vm::VirtualMachine; /// PROMETEU Firmware. -/// -/// The central orchestrator of the console. The firmware acts as the +/// +/// The central orchestrator of the console. The firmware acts as the /// "Control Unit", managing the high-level state machine of the system. /// -/// It is responsible for transitioning between different modes of operation, -/// such as showing the splash screen, running the Hub (launcher), or +/// It is responsible for transitioning between different modes of operation, +/// such as showing the splash screen, running the Hub (launcher), or /// executing a game/app. /// /// ### Execution Loop: -/// The firmware is designed to be ticked once per frame (60Hz). During each +/// The firmware is designed to be ticked once per frame (60Hz). During each /// tick, it: /// 1. Updates peripherals with the latest input signals. /// 2. Delegates the logic update to the current active state. @@ -74,13 +74,18 @@ impl Firmware { self.change_state(next_state, signals, hw); } } - + /// Transitions the system to a new state, handling lifecycle hooks. - pub fn change_state(&mut self, new_state: FirmwareState, signals: &InputSignals, hw: &mut dyn HardwareBridge) { + pub fn change_state( + &mut self, + new_state: FirmwareState, + signals: &InputSignals, + hw: &mut dyn HardwareBridge, + ) { self.on_exit(signals, hw); self.state = new_state; self.state_initialized = false; - + // Enter the new state immediately to avoid "empty" frames during transitions. self.on_enter(signals, hw); self.state_initialized = true; @@ -109,7 +114,11 @@ impl Firmware { /// Dispatches the `on_update` event to the current state implementation. /// Returns an optional `FirmwareState` if a transition is requested. - fn on_update(&mut self, signals: &InputSignals, hw: &mut dyn HardwareBridge) -> Option { + fn on_update( + &mut self, + signals: &InputSignals, + hw: &mut dyn HardwareBridge, + ) -> Option { let mut req = PrometeuContext { vm: &mut self.vm, os: &mut self.os, @@ -149,9 +158,9 @@ impl Firmware { FirmwareState::AppCrashes(s) => s.on_exit(&mut req), } } - + pub fn load_cartridge(&mut self, cartridge: Cartridge) { self.state = FirmwareState::LoadCartridge(LoadCartridgeStep { cartridge }); self.state_initialized = false; } -} \ No newline at end of file +} diff --git a/crates/console/prometeu-firmware/src/firmware/firmware_state.rs b/crates/console/prometeu-firmware/src/firmware/firmware_state.rs index d4bad12f..e388721b 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware_state.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware_state.rs @@ -15,4 +15,4 @@ pub enum FirmwareState { LoadCartridge(LoadCartridgeStep), GameRunning(GameRunningStep), AppCrashes(AppCrashesStep), -} \ No newline at end of file +} diff --git a/crates/console/prometeu-firmware/src/firmware/firmware_step_crash_screen.rs b/crates/console/prometeu-firmware/src/firmware/firmware_step_crash_screen.rs index 03993014..882164d8 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware_step_crash_screen.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware_step_crash_screen.rs @@ -1,7 +1,7 @@ -use prometeu_hal::log::{LogLevel, LogSource}; -use prometeu_hal::color::Color; use crate::firmware::firmware_state::{FirmwareState, LaunchHubStep}; use crate::firmware::prometeu_context::PrometeuContext; +use prometeu_hal::color::Color; +use prometeu_hal::log::{LogLevel, LogSource}; #[derive(Debug, Clone)] pub struct AppCrashesStep { @@ -27,9 +27,9 @@ impl AppCrashesStep { if ctx.hw.pad().start().down { return Some(FirmwareState::LaunchHub(LaunchHubStep)); } - + None } pub fn on_exit(&mut self, _ctx: &mut PrometeuContext) {} -} \ No newline at end of file +} diff --git a/crates/console/prometeu-firmware/src/firmware/firmware_step_game_running.rs b/crates/console/prometeu-firmware/src/firmware/firmware_step_game_running.rs index d2202b04..8bdbd737 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware_step_game_running.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware_step_game_running.rs @@ -1,6 +1,6 @@ -use prometeu_hal::log::{LogLevel, LogSource}; use crate::firmware::firmware_state::{AppCrashesStep, FirmwareState}; use crate::firmware::prometeu_context::PrometeuContext; +use prometeu_hal::log::{LogLevel, LogSource}; #[derive(Debug, Clone)] pub struct GameRunningStep; @@ -12,7 +12,7 @@ impl GameRunningStep { pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option { let result = ctx.os.tick(ctx.vm, ctx.signals, ctx.hw); - + if !ctx.os.logical_frame_active { ctx.hw.gfx_mut().present(); } diff --git a/crates/console/prometeu-firmware/src/firmware/firmware_step_hub_home.rs b/crates/console/prometeu-firmware/src/firmware/firmware_step_hub_home.rs index 5eb94ed6..55acf96f 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware_step_hub_home.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware_step_hub_home.rs @@ -27,15 +27,15 @@ impl HubHomeStep { // Renders the System App window borders ctx.hub.render(ctx.os, ctx.hw); - + ctx.hw.gfx_mut().present(); - + if let Some(err) = error { return Some(FirmwareState::AppCrashes(AppCrashesStep { error: err })); } - + None } pub fn on_exit(&mut self, _ctx: &mut PrometeuContext) {} -} \ No newline at end of file +} diff --git a/crates/console/prometeu-firmware/src/firmware/firmware_step_launch_hub.rs b/crates/console/prometeu-firmware/src/firmware/firmware_step_launch_hub.rs index a110799b..3301e1bb 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware_step_launch_hub.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware_step_launch_hub.rs @@ -1,8 +1,8 @@ use crate::firmware::boot_target::BootTarget; use crate::firmware::firmware_state::{FirmwareState, HubHomeStep, LoadCartridgeStep}; use crate::firmware::prometeu_context::PrometeuContext; -use prometeu_hal::log::{LogLevel, LogSource}; use prometeu_hal::cartridge_loader::CartridgeLoader; +use prometeu_hal::log::{LogLevel, LogSource}; #[derive(Debug, Clone)] pub struct LaunchHubStep; @@ -22,7 +22,12 @@ impl LaunchHubStep { return Some(FirmwareState::LoadCartridge(LoadCartridgeStep { cartridge })); } Err(e) => { - ctx.os.log(LogLevel::Error, LogSource::Pos, 0, format!("Failed to auto-load cartridge: {:?}", e)); + ctx.os.log( + LogLevel::Error, + LogSource::Pos, + 0, + format!("Failed to auto-load cartridge: {:?}", e), + ); } } } @@ -30,4 +35,4 @@ impl LaunchHubStep { } pub fn on_exit(&mut self, _ctx: &mut PrometeuContext) {} -} \ No newline at end of file +} diff --git a/crates/console/prometeu-firmware/src/firmware/firmware_step_load_cartridge.rs b/crates/console/prometeu-firmware/src/firmware/firmware_step_load_cartridge.rs index c0122f11..fa406a17 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware_step_load_cartridge.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware_step_load_cartridge.rs @@ -1,8 +1,8 @@ use crate::firmware::firmware_state::{FirmwareState, GameRunningStep, HubHomeStep}; use crate::firmware::prometeu_context::PrometeuContext; -use prometeu_hal::log::{LogLevel, LogSource}; use prometeu_hal::cartridge::{AppMode, Cartridge}; use prometeu_hal::color::Color; +use prometeu_hal::log::{LogLevel, LogSource}; use prometeu_hal::window::Rect; #[derive(Debug, Clone)] @@ -12,13 +12,18 @@ pub struct LoadCartridgeStep { impl LoadCartridgeStep { pub fn on_enter(&mut self, ctx: &mut PrometeuContext) { - ctx.os.log(LogLevel::Info, LogSource::Pos, 0, format!("Loading cartridge: {}", self.cartridge.title)); - + ctx.os.log( + LogLevel::Info, + LogSource::Pos, + 0, + format!("Loading cartridge: {}", self.cartridge.title), + ); + // Initialize Asset Manager ctx.hw.assets_mut().initialize_for_cartridge( self.cartridge.asset_table.clone(), self.cartridge.preload.clone(), - self.cartridge.assets.clone() + self.cartridge.assets.clone(), ); ctx.os.initialize_vm(ctx.vm, &self.cartridge); @@ -29,10 +34,10 @@ impl LoadCartridgeStep { let id = ctx.hub.window_manager.add_window( self.cartridge.title.clone(), Rect { x: 40, y: 20, w: 240, h: 140 }, - Color::WHITE + Color::WHITE, ); ctx.hub.window_manager.set_focus(id); - + // System apps do not change the firmware state to GameRunning. // They run in the background or via windows in the Hub. return Some(FirmwareState::HubHome(HubHomeStep)); @@ -42,4 +47,4 @@ impl LoadCartridgeStep { } pub fn on_exit(&mut self, _ctx: &mut PrometeuContext) {} -} \ No newline at end of file +} diff --git a/crates/console/prometeu-firmware/src/firmware/firmware_step_reset.rs b/crates/console/prometeu-firmware/src/firmware/firmware_step_reset.rs index 990ade0e..4c14c749 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware_step_reset.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware_step_reset.rs @@ -1,7 +1,7 @@ -use prometeu_hal::log::{LogLevel, LogSource}; use crate::firmware::boot_target::BootTarget; use crate::firmware::firmware_state::{FirmwareState, LaunchHubStep, SplashScreenStep}; use crate::firmware::prometeu_context::PrometeuContext; +use prometeu_hal::log::{LogLevel, LogSource}; #[derive(Debug, Clone)] pub struct ResetStep; @@ -20,4 +20,4 @@ impl ResetStep { } pub fn on_exit(&mut self, _ctx: &mut PrometeuContext) {} -} \ No newline at end of file +} diff --git a/crates/console/prometeu-firmware/src/firmware/firmware_step_splash_screen.rs b/crates/console/prometeu-firmware/src/firmware/firmware_step_splash_screen.rs index 88c923c9..6695a7dd 100644 --- a/crates/console/prometeu-firmware/src/firmware/firmware_step_splash_screen.rs +++ b/crates/console/prometeu-firmware/src/firmware/firmware_step_splash_screen.rs @@ -1,7 +1,7 @@ use crate::firmware::firmware_state::{FirmwareState, LaunchHubStep}; use crate::firmware::prometeu_context::PrometeuContext; -use prometeu_hal::log::{LogLevel, LogSource}; use prometeu_hal::color::Color; +use prometeu_hal::log::{LogLevel, LogSource}; #[derive(Debug, Clone)] pub struct SplashScreenStep { @@ -17,7 +17,7 @@ impl SplashScreenStep { pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option { const ANIMATION_DURATION: u32 = 60; // 1 second at 60fps - const TOTAL_DURATION: u32 = 240; // 4 seconds total (updated from 2s based on total_duration logic) + const TOTAL_DURATION: u32 = 240; // 4 seconds total (updated from 2s based on total_duration logic) // Update peripherals for input ctx.hw.pad_mut().begin_frame(ctx.signals); @@ -28,7 +28,7 @@ impl SplashScreenStep { // Draw expanding square let (sw, sh) = ctx.hw.gfx().size(); let max_size = (sw.min(sh) as i32 / 2).max(1); - + let current_size = if self.frame < ANIMATION_DURATION { (max_size * (self.frame as i32 + 1)) / ANIMATION_DURATION as i32 } else { @@ -37,7 +37,7 @@ impl SplashScreenStep { let x = (sw as i32 - current_size) / 2; let y = (sh as i32 - current_size) / 2; - + ctx.hw.gfx_mut().fill_rect(x, y, current_size, current_size, Color::WHITE); ctx.hw.gfx_mut().present(); @@ -57,4 +57,4 @@ impl SplashScreenStep { } pub fn on_exit(&mut self, _ctx: &mut PrometeuContext) {} -} \ No newline at end of file +} diff --git a/crates/console/prometeu-firmware/src/firmware/mod.rs b/crates/console/prometeu-firmware/src/firmware/mod.rs index 4c71a92f..32faa653 100644 --- a/crates/console/prometeu-firmware/src/firmware/mod.rs +++ b/crates/console/prometeu-firmware/src/firmware/mod.rs @@ -1,14 +1,14 @@ +mod boot_target; mod firmware; pub mod firmware_state; -mod boot_target; +pub(crate) mod firmware_step_crash_screen; +pub(crate) mod firmware_step_game_running; +pub(crate) mod firmware_step_hub_home; +pub(crate) mod firmware_step_launch_hub; +pub(crate) mod firmware_step_load_cartridge; pub(crate) mod firmware_step_reset; pub(crate) mod firmware_step_splash_screen; -pub(crate) mod firmware_step_launch_hub; -pub(crate) mod firmware_step_hub_home; -pub(crate) mod firmware_step_load_cartridge; -pub(crate) mod firmware_step_game_running; -pub(crate) mod firmware_step_crash_screen; mod prometeu_context; pub use boot_target::BootTarget; diff --git a/crates/console/prometeu-firmware/src/firmware/prometeu_context.rs b/crates/console/prometeu-firmware/src/firmware/prometeu_context.rs index 3095cd37..2e06bcde 100644 --- a/crates/console/prometeu-firmware/src/firmware/prometeu_context.rs +++ b/crates/console/prometeu-firmware/src/firmware/prometeu_context.rs @@ -1,7 +1,7 @@ +use crate::firmware::boot_target::BootTarget; use prometeu_hal::{HardwareBridge, InputSignals}; use prometeu_system::{PrometeuHub, VirtualMachineRuntime}; use prometeu_vm::VirtualMachine; -use crate::firmware::boot_target::BootTarget; pub struct PrometeuContext<'a> { pub vm: &'a mut VirtualMachine, @@ -10,4 +10,4 @@ pub struct PrometeuContext<'a> { pub boot_target: &'a BootTarget, pub signals: &'a InputSignals, pub hw: &'a mut dyn HardwareBridge, -} \ No newline at end of file +} diff --git a/crates/console/prometeu-hal/src/asset.rs b/crates/console/prometeu-hal/src/asset.rs index 195427f3..9957240c 100644 --- a/crates/console/prometeu-hal/src/asset.rs +++ b/crates/console/prometeu-hal/src/asset.rs @@ -65,16 +65,10 @@ pub struct SlotRef { impl SlotRef { pub fn gfx(index: usize) -> Self { - Self { - asset_type: BankType::TILES, - index, - } + Self { asset_type: BankType::TILES, index } } pub fn audio(index: usize) -> Self { - Self { - asset_type: BankType::SOUNDS, - index, - } + Self { asset_type: BankType::SOUNDS, index } } } diff --git a/crates/console/prometeu-hal/src/asset_bridge.rs b/crates/console/prometeu-hal/src/asset_bridge.rs index 98bf6d83..fa57e8ff 100644 --- a/crates/console/prometeu-hal/src/asset_bridge.rs +++ b/crates/console/prometeu-hal/src/asset_bridge.rs @@ -1,4 +1,6 @@ -use crate::asset::{AssetEntry, BankStats, BankType, HandleId, LoadStatus, PreloadEntry, SlotRef, SlotStats}; +use crate::asset::{ + AssetEntry, BankStats, BankType, HandleId, LoadStatus, PreloadEntry, SlotRef, SlotStats, +}; pub trait AssetBridge { fn initialize_for_cartridge( diff --git a/crates/console/prometeu-hal/src/audio_bridge.rs b/crates/console/prometeu-hal/src/audio_bridge.rs index e8c0856a..116fd12c 100644 --- a/crates/console/prometeu-hal/src/audio_bridge.rs +++ b/crates/console/prometeu-hal/src/audio_bridge.rs @@ -1,5 +1,5 @@ -use std::sync::Arc; use crate::sample::Sample; +use std::sync::Arc; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum LoopMode { diff --git a/crates/console/prometeu-hal/src/cartridge_loader.rs b/crates/console/prometeu-hal/src/cartridge_loader.rs index 459751c8..7919d8f9 100644 --- a/crates/console/prometeu-hal/src/cartridge_loader.rs +++ b/crates/console/prometeu-hal/src/cartridge_loader.rs @@ -1,6 +1,6 @@ +use crate::cartridge::{Cartridge, CartridgeDTO, CartridgeError, CartridgeManifest}; use std::fs; use std::path::Path; -use crate::cartridge::{Cartridge, CartridgeDTO, CartridgeError, CartridgeManifest}; pub struct CartridgeLoader; @@ -30,8 +30,10 @@ impl DirectoryCartridgeLoader { return Err(CartridgeError::InvalidManifest); } - let manifest_content = fs::read_to_string(manifest_path).map_err(|_| CartridgeError::IoError)?; - let manifest: CartridgeManifest = serde_json::from_str(&manifest_content).map_err(|_| CartridgeError::InvalidManifest)?; + let manifest_content = + fs::read_to_string(manifest_path).map_err(|_| CartridgeError::IoError)?; + let manifest: CartridgeManifest = + serde_json::from_str(&manifest_content).map_err(|_| CartridgeError::InvalidManifest)?; // Additional validation as per requirements if manifest.magic != "PMTU" { diff --git a/crates/console/prometeu-hal/src/color.rs b/crates/console/prometeu-hal/src/color.rs index 4e0fcffc..d5b440bc 100644 --- a/crates/console/prometeu-hal/src/color.rs +++ b/crates/console/prometeu-hal/src/color.rs @@ -1,12 +1,12 @@ /// Represents a 16-bit color in the RGB565 format. -/// +/// /// The RGB565 format is a common high-color representation for embedded systems: /// - **Red**: 5 bits (0..31) /// - **Green**: 6 bits (0..63) /// - **Blue**: 5 bits (0..31) /// -/// Prometeu does not have a hardware alpha channel. Transparency is achieved -/// by using a specific color key (Magenta / 0xF81F) which the GFX engine +/// Prometeu does not have a hardware alpha channel. Transparency is achieved +/// by using a specific color key (Magenta / 0xF81F) which the GFX engine /// skips during rendering. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct Color(pub u16); @@ -55,7 +55,7 @@ impl Color { pub const fn gray_scale(c: u8) -> Self { Self::rgb(c, c, c) } - + pub const fn from_raw(raw: u16) -> Self { Self(raw) } @@ -72,5 +72,4 @@ impl Color { let hex = r8 << 16 | g8 << 8 | b8; hex as i32 } - } diff --git a/crates/console/prometeu-hal/src/debugger_protocol.rs b/crates/console/prometeu-hal/src/debugger_protocol.rs index e9779149..acb929b0 100644 --- a/crates/console/prometeu-hal/src/debugger_protocol.rs +++ b/crates/console/prometeu-hal/src/debugger_protocol.rs @@ -1,6 +1,6 @@ -use serde::{Deserialize, Serialize}; -use prometeu_bytecode::Value; use crate::cartridge::AppMode; +use prometeu_bytecode::Value; +use serde::{Deserialize, Serialize}; pub const DEVTOOLS_PROTOCOL_VERSION: u32 = 1; @@ -33,22 +33,11 @@ pub enum DebugCommand { #[serde(tag = "type")] pub enum DebugResponse { #[serde(rename = "handshake")] - Handshake { - protocol_version: u32, - runtime_version: String, - cartridge: HandshakeCartridge, - }, + Handshake { protocol_version: u32, runtime_version: String, cartridge: HandshakeCartridge }, #[serde(rename = "getState")] - GetState { - pc: usize, - stack_top: Vec, - frame_index: u64, - app_id: u32, - }, + GetState { pc: usize, stack_top: Vec, frame_index: u64, app_id: u32 }, #[serde(rename = "breakpoints")] - Breakpoints { - pcs: Vec, - }, + Breakpoints { pcs: Vec }, } #[derive(Debug, Serialize, Deserialize)] @@ -63,16 +52,9 @@ pub struct HandshakeCartridge { #[serde(tag = "event")] pub enum DebugEvent { #[serde(rename = "breakpointHit")] - BreakpointHit { - pc: usize, - frame_index: u64, - }, + BreakpointHit { pc: usize, frame_index: u64 }, #[serde(rename = "log")] - Log { - level: String, - source: String, - msg: String, - }, + Log { level: String, source: String, msg: String }, #[serde(rename = "telemetry")] Telemetry { frame_index: u64, @@ -90,12 +72,7 @@ pub enum DebugEvent { audio_slots_occupied: u32, }, #[serde(rename = "cert")] - Cert { - rule: String, - used: u64, - limit: u64, - frame_index: u64, - }, + Cert { rule: String, used: u64, limit: u64, frame_index: u64 }, } #[cfg(test)] diff --git a/crates/console/prometeu-hal/src/gfx_bridge.rs b/crates/console/prometeu-hal/src/gfx_bridge.rs index ef6e3d59..33295999 100644 --- a/crates/console/prometeu-hal/src/gfx_bridge.rs +++ b/crates/console/prometeu-hal/src/gfx_bridge.rs @@ -25,7 +25,15 @@ pub trait GfxBridge { fn draw_circle_lines(&mut self, xc: i32, yc: i32, x: i32, y: i32, color: Color); fn draw_disc(&mut self, x: i32, y: i32, r: i32, border_color: Color, fill_color: Color); fn draw_rect(&mut self, x: i32, y: i32, w: i32, h: i32, color: Color); - fn draw_square(&mut self, x: i32, y: i32, w: i32, h: i32, border_color: Color, fill_color: Color); + fn draw_square( + &mut self, + x: i32, + y: i32, + w: i32, + h: i32, + border_color: Color, + fill_color: Color, + ); fn draw_horizontal_line(&mut self, x0: i32, x1: i32, y: i32, color: Color); fn draw_vertical_line(&mut self, x: i32, y0: i32, y1: i32, color: Color); fn present(&mut self); diff --git a/crates/console/prometeu-hal/src/host_return.rs b/crates/console/prometeu-hal/src/host_return.rs index b5222bbe..ad395f0a 100644 --- a/crates/console/prometeu-hal/src/host_return.rs +++ b/crates/console/prometeu-hal/src/host_return.rs @@ -1,8 +1,8 @@ -use prometeu_bytecode::{Value, TRAP_OOB}; use crate::vm_fault::VmFault; +use prometeu_bytecode::{TRAP_OOB, Value}; pub struct HostReturn<'a> { - stack: &'a mut Vec + stack: &'a mut Vec, } impl<'a> HostReturn<'a> { diff --git a/crates/console/prometeu-hal/src/lib.rs b/crates/console/prometeu-hal/src/lib.rs index 9b926251..f8857506 100644 --- a/crates/console/prometeu-hal/src/lib.rs +++ b/crates/console/prometeu-hal/src/lib.rs @@ -1,31 +1,31 @@ +pub mod asset; pub mod asset_bridge; pub mod audio_bridge; +pub mod button; +pub mod cartridge; +pub mod cartridge_loader; +pub mod color; +pub mod debugger_protocol; pub mod gfx_bridge; pub mod hardware_bridge; pub mod host_context; pub mod host_return; +pub mod input_signals; +pub mod log; +pub mod native_helpers; pub mod native_interface; pub mod pad_bridge; -pub mod touch_bridge; -pub mod native_helpers; -pub mod input_signals; -pub mod cartridge; -pub mod cartridge_loader; -pub mod debugger_protocol; -pub mod asset; -pub mod color; -pub mod button; -pub mod tile; -pub mod tile_layer; -pub mod tile_bank; +pub mod sample; pub mod sound_bank; pub mod sprite; -pub mod sample; -pub mod window; pub mod syscalls; pub mod telemetry; -pub mod log; +pub mod tile; +pub mod tile_bank; +pub mod tile_layer; +pub mod touch_bridge; pub mod vm_fault; +pub mod window; pub use asset_bridge::AssetBridge; pub use audio_bridge::{AudioBridge, LoopMode}; @@ -34,7 +34,7 @@ pub use hardware_bridge::HardwareBridge; pub use host_context::{HostContext, HostContextProvider}; pub use host_return::HostReturn; pub use input_signals::InputSignals; +pub use native_helpers::{expect_bool, expect_bounded, expect_int}; pub use native_interface::{NativeInterface, SyscallId}; pub use pad_bridge::PadBridge; pub use touch_bridge::TouchBridge; -pub use native_helpers::{expect_bool, expect_bounded, expect_int}; \ No newline at end of file diff --git a/crates/console/prometeu-hal/src/log/log_service.rs b/crates/console/prometeu-hal/src/log/log_service.rs index d3065683..0d8f015c 100644 --- a/crates/console/prometeu-hal/src/log/log_service.rs +++ b/crates/console/prometeu-hal/src/log/log_service.rs @@ -1,5 +1,5 @@ -use std::collections::VecDeque; use crate::log::{LogEvent, LogLevel, LogSource}; +use std::collections::VecDeque; pub struct LogService { events: VecDeque, @@ -9,14 +9,18 @@ pub struct LogService { impl LogService { pub fn new(capacity: usize) -> Self { - Self { - events: VecDeque::with_capacity(capacity), - capacity, - next_seq: 0, - } + Self { events: VecDeque::with_capacity(capacity), capacity, next_seq: 0 } } - pub fn log(&mut self, ts_ms: u64, frame: u64, level: LogLevel, source: LogSource, tag: u16, msg: String) { + pub fn log( + &mut self, + ts_ms: u64, + frame: u64, + level: LogLevel, + source: LogSource, + tag: u16, + msg: String, + ) { if self.events.len() >= self.capacity { self.events.pop_front(); } @@ -55,7 +59,7 @@ mod tests { service.log(100, 1, LogLevel::Info, LogSource::Pos, 0, "Log 1".to_string()); service.log(110, 1, LogLevel::Info, LogSource::Pos, 0, "Log 2".to_string()); service.log(120, 1, LogLevel::Info, LogSource::Pos, 0, "Log 3".to_string()); - + assert_eq!(service.events.len(), 3); assert_eq!(service.events[0].msg, "Log 1"); diff --git a/crates/console/prometeu-hal/src/log/mod.rs b/crates/console/prometeu-hal/src/log/mod.rs index dd576c11..691d461f 100644 --- a/crates/console/prometeu-hal/src/log/mod.rs +++ b/crates/console/prometeu-hal/src/log/mod.rs @@ -1,7 +1,7 @@ -mod log_level; -mod log_source; mod log_event; +mod log_level; mod log_service; +mod log_source; pub use log_event::LogEvent; pub use log_level::LogLevel; diff --git a/crates/console/prometeu-hal/src/native_helpers.rs b/crates/console/prometeu-hal/src/native_helpers.rs index 6cb7a2c4..df8fcd08 100644 --- a/crates/console/prometeu-hal/src/native_helpers.rs +++ b/crates/console/prometeu-hal/src/native_helpers.rs @@ -1,5 +1,5 @@ -use prometeu_bytecode::{Value, TRAP_TYPE}; use crate::vm_fault::VmFault; +use prometeu_bytecode::{TRAP_TYPE, Value}; pub fn expect_bounded(args: &[Value], idx: usize) -> Result { args.get(idx) diff --git a/crates/console/prometeu-hal/src/native_interface.rs b/crates/console/prometeu-hal/src/native_interface.rs index 0259d679..87f5aa2b 100644 --- a/crates/console/prometeu-hal/src/native_interface.rs +++ b/crates/console/prometeu-hal/src/native_interface.rs @@ -1,7 +1,7 @@ -use prometeu_bytecode::Value; use crate::host_context::HostContext; use crate::host_return::HostReturn; use crate::vm_fault::VmFault; +use prometeu_bytecode::Value; pub type SyscallId = u32; @@ -11,5 +11,11 @@ pub trait NativeInterface { /// ABI Rule: Arguments for the syscall are passed in `args`. /// /// Returns are written via `ret`. - fn syscall(&mut self, id: SyscallId, args: &[Value], ret: &mut HostReturn, ctx: &mut HostContext) -> Result<(), VmFault>; + fn syscall( + &mut self, + id: SyscallId, + args: &[Value], + ret: &mut HostReturn, + ctx: &mut HostContext, + ) -> Result<(), VmFault>; } diff --git a/crates/console/prometeu-hal/src/sample.rs b/crates/console/prometeu-hal/src/sample.rs index 5ced72bf..7c5356f7 100644 --- a/crates/console/prometeu-hal/src/sample.rs +++ b/crates/console/prometeu-hal/src/sample.rs @@ -7,12 +7,7 @@ pub struct Sample { impl Sample { pub fn new(sample_rate: u32, data: Vec) -> Self { - Self { - sample_rate, - data, - loop_start: None, - loop_end: None, - } + Self { sample_rate, data, loop_start: None, loop_end: None } } pub fn with_loop(mut self, start: u32, end: u32) -> Self { diff --git a/crates/console/prometeu-hal/src/sound_bank.rs b/crates/console/prometeu-hal/src/sound_bank.rs index 97edfb3b..136ba7c3 100644 --- a/crates/console/prometeu-hal/src/sound_bank.rs +++ b/crates/console/prometeu-hal/src/sound_bank.rs @@ -1,9 +1,9 @@ -use std::sync::Arc; use crate::sample::Sample; +use std::sync::Arc; /// A container for audio assets. -/// -/// A SoundBank stores multiple audio samples that can be played by the +/// +/// A SoundBank stores multiple audio samples that can be played by the /// audio subsystem. pub struct SoundBank { pub samples: Vec>, diff --git a/crates/console/prometeu-hal/src/syscalls.rs b/crates/console/prometeu-hal/src/syscalls.rs index 6936bf6a..c56bc4e2 100644 --- a/crates/console/prometeu-hal/src/syscalls.rs +++ b/crates/console/prometeu-hal/src/syscalls.rs @@ -1,8 +1,8 @@ /// Enumeration of all System Calls (Syscalls) available in the Prometeu environment. /// -/// Syscalls are the primary mechanism for a program running in the Virtual Machine -/// to interact with the outside world (Hardware, OS, Filesystem). -/// +/// Syscalls are the primary mechanism for a program running in the Virtual Machine +/// to interact with the outside world (Hardware, OS, Filesystem). +/// /// Each Syscall has a unique 32-bit ID. The IDs are grouped by category: /// - **0x0xxx**: System & OS Control /// - **0x1xxx**: Graphics (GFX) @@ -301,8 +301,8 @@ impl Syscall { // --- FS --- Self::FsOpen => 1, - Self::FsRead => 1, // bytes read - Self::FsWrite => 1, // bytes written + Self::FsRead => 1, // bytes read + Self::FsWrite => 1, // bytes written Self::FsClose => 0, Self::FsListDir => 1, // entries count/handle (TBD) Self::FsExists => 1, diff --git a/crates/console/prometeu-hal/src/telemetry.rs b/crates/console/prometeu-hal/src/telemetry.rs index 2dbe4f31..ce8f99e2 100644 --- a/crates/console/prometeu-hal/src/telemetry.rs +++ b/crates/console/prometeu-hal/src/telemetry.rs @@ -38,7 +38,12 @@ impl Certifier { Self { config } } - pub fn evaluate(&self, telemetry: &TelemetryFrame, log_service: &mut LogService, ts_ms: u64) -> usize { + pub fn evaluate( + &self, + telemetry: &TelemetryFrame, + log_service: &mut LogService, + ts_ms: u64, + ) -> usize { if !self.config.enabled { return 0; } @@ -53,7 +58,10 @@ impl Certifier { LogLevel::Warn, LogSource::Pos, 0xCA01, - format!("Cert: cycles_used exceeded budget ({} > {})", telemetry.cycles_used, budget), + format!( + "Cert: cycles_used exceeded budget ({} > {})", + telemetry.cycles_used, budget + ), ); violations += 1; } @@ -67,7 +75,10 @@ impl Certifier { LogLevel::Warn, LogSource::Pos, 0xCA02, - format!("Cert: syscalls per frame exceeded limit ({} > {})", telemetry.syscalls, limit), + format!( + "Cert: syscalls per frame exceeded limit ({} > {})", + telemetry.syscalls, limit + ), ); violations += 1; } @@ -81,7 +92,10 @@ impl Certifier { LogLevel::Warn, LogSource::Pos, 0xCA03, - format!("Cert: host_cpu_time_us exceeded limit ({} > {})", telemetry.host_cpu_time_us, limit), + format!( + "Cert: host_cpu_time_us exceeded limit ({} > {})", + telemetry.host_cpu_time_us, limit + ), ); violations += 1; } @@ -106,15 +120,15 @@ mod tests { }; let cert = Certifier::new(config); let mut ls = LogService::new(10); - + let mut tel = TelemetryFrame::default(); tel.cycles_used = 150; tel.syscalls = 10; tel.host_cpu_time_us = 500; - + let violations = cert.evaluate(&tel, &mut ls, 1000); assert_eq!(violations, 2); - + let logs = ls.get_recent(10); assert_eq!(logs.len(), 2); assert!(logs[0].msg.contains("cycles_used")); diff --git a/crates/console/prometeu-hal/src/tile_bank.rs b/crates/console/prometeu-hal/src/tile_bank.rs index 185b52f3..15e0cac2 100644 --- a/crates/console/prometeu-hal/src/tile_bank.rs +++ b/crates/console/prometeu-hal/src/tile_bank.rs @@ -12,9 +12,9 @@ pub enum TileSize { } /// A container for graphical assets. -/// -/// A TileBank stores both the raw pixel data (as palette indices) and the -/// color palettes themselves. This encapsulates all the information needed +/// +/// A TileBank stores both the raw pixel data (as palette indices) and the +/// color palettes themselves. This encapsulates all the information needed /// to render a set of tiles. pub struct TileBank { /// Dimension of each individual tile in the bank. @@ -64,7 +64,7 @@ impl TileBank { /// Maps a 4-bit index to a real RGB565 Color using the specified palette. pub fn resolve_color(&self, palette_id: u8, pixel_index: u8) -> Color { - // Hardware Rule: Index 0 is always transparent. + // Hardware Rule: Index 0 is always transparent. // We use Magenta as the 'transparent' signal color during composition. if pixel_index == 0 { return Color::COLOR_KEY; diff --git a/crates/console/prometeu-hal/src/tile_layer.rs b/crates/console/prometeu-hal/src/tile_layer.rs index 629add9c..07078ebd 100644 --- a/crates/console/prometeu-hal/src/tile_layer.rs +++ b/crates/console/prometeu-hal/src/tile_layer.rs @@ -10,11 +10,7 @@ pub struct TileMap { impl TileMap { fn create(width: usize, height: usize) -> Self { - Self { - width, - height, - tiles: vec![Tile::default(); width * height], - } + Self { width, height, tiles: vec![Tile::default(); width * height] } } pub fn set_tile(&mut self, x: usize, y: usize, tile: Tile) { @@ -24,7 +20,6 @@ impl TileMap { } } - pub struct TileLayer { pub bank_id: u8, pub tile_size: TileSize, @@ -33,11 +28,7 @@ pub struct TileLayer { impl TileLayer { fn create(width: usize, height: usize, tile_size: TileSize) -> Self { - Self { - bank_id: 0, - tile_size, - map: TileMap::create(width, height), - } + Self { bank_id: 0, tile_size, map: TileMap::create(width, height) } } } @@ -62,11 +53,7 @@ pub struct ScrollableTileLayer { impl ScrollableTileLayer { pub fn new(width: usize, height: usize, tile_size: TileSize) -> Self { - Self { - layer: TileLayer::create(width, height, tile_size), - scroll_x: 0, - scroll_y: 0, - } + Self { layer: TileLayer::create(width, height, tile_size), scroll_x: 0, scroll_y: 0 } } } @@ -89,9 +76,7 @@ pub struct HudTileLayer { impl HudTileLayer { pub fn new(width: usize, height: usize) -> Self { - Self { - layer: TileLayer::create(width, height, Size8), - } + Self { layer: TileLayer::create(width, height, Size8) } } } diff --git a/crates/console/prometeu-hal/src/touch_bridge.rs b/crates/console/prometeu-hal/src/touch_bridge.rs index 1d83ccde..6baa6df9 100644 --- a/crates/console/prometeu-hal/src/touch_bridge.rs +++ b/crates/console/prometeu-hal/src/touch_bridge.rs @@ -1,5 +1,5 @@ -use crate::input_signals::InputSignals; use crate::button::Button; +use crate::input_signals::InputSignals; pub trait TouchBridge { fn begin_frame(&mut self, signals: &InputSignals); diff --git a/crates/console/prometeu-hal/src/vm_fault.rs b/crates/console/prometeu-hal/src/vm_fault.rs index 1db51d3c..afd62dcf 100644 --- a/crates/console/prometeu-hal/src/vm_fault.rs +++ b/crates/console/prometeu-hal/src/vm_fault.rs @@ -3,4 +3,4 @@ pub enum VmFault { Trap(u32, String), Panic(String), Unavailable, -} \ No newline at end of file +} diff --git a/crates/console/prometeu-system/src/lib.rs b/crates/console/prometeu-system/src/lib.rs index 77857879..1c151f13 100644 --- a/crates/console/prometeu-system/src/lib.rs +++ b/crates/console/prometeu-system/src/lib.rs @@ -1,6 +1,6 @@ -mod virtual_machine_runtime; -mod services; mod programs; +mod services; +mod virtual_machine_runtime; pub use programs::PrometeuHub; pub use services::fs; diff --git a/crates/console/prometeu-system/src/programs/mod.rs b/crates/console/prometeu-system/src/programs/mod.rs index 97e3702e..1a48ee4a 100644 --- a/crates/console/prometeu-system/src/programs/mod.rs +++ b/crates/console/prometeu-system/src/programs/mod.rs @@ -1,3 +1,3 @@ mod prometeu_hub; -pub use prometeu_hub::PrometeuHub; \ No newline at end of file +pub use prometeu_hub::PrometeuHub; diff --git a/crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs b/crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs index c2634b0c..e34677fc 100644 --- a/crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs +++ b/crates/console/prometeu-system/src/programs/prometeu_hub/prometeu_hub.rs @@ -1,9 +1,9 @@ -use crate::programs::prometeu_hub::window_manager::WindowManager; -use prometeu_hal::log::{LogLevel, LogSource}; -use prometeu_hal::color::Color; -use prometeu_hal::HardwareBridge; -use prometeu_hal::window::Rect; use crate::VirtualMachineRuntime; +use crate::programs::prometeu_hub::window_manager::WindowManager; +use prometeu_hal::HardwareBridge; +use prometeu_hal::color::Color; +use prometeu_hal::log::{LogLevel, LogSource}; +use prometeu_hal::window::Rect; /// PrometeuHub: Launcher and system UI environment. pub struct PrometeuHub { @@ -12,9 +12,7 @@ pub struct PrometeuHub { impl PrometeuHub { pub fn new() -> Self { - Self { - window_manager: WindowManager::new(), - } + Self { window_manager: WindowManager::new() } } pub fn init(&mut self) { @@ -28,16 +26,29 @@ impl PrometeuHub { if hw.pad().a().pressed { os.log(LogLevel::Debug, LogSource::Hub, 0, "window A opened".to_string()); - next_window = Some(("Green Window".to_string(), Rect { x: 0, y: 0, w: 160, h: 90 }, Color::GREEN)); + next_window = Some(( + "Green Window".to_string(), + Rect { x: 0, y: 0, w: 160, h: 90 }, + Color::GREEN, + )); } else if hw.pad().b().pressed { os.log(LogLevel::Debug, LogSource::Hub, 0, "window B opened".to_string()); - next_window = Some(("Indigo Window".to_string(), Rect { x: 160, y: 0, w: 160, h: 90 }, Color::INDIGO)); + next_window = Some(( + "Indigo Window".to_string(), + Rect { x: 160, y: 0, w: 160, h: 90 }, + Color::INDIGO, + )); } else if hw.pad().x().pressed { os.log(LogLevel::Debug, LogSource::Hub, 0, "window X opened".to_string()); - next_window = Some(("Yellow Window".to_string(), Rect { x: 0, y: 90, w: 160, h: 90 }, Color::YELLOW)); + next_window = Some(( + "Yellow Window".to_string(), + Rect { x: 0, y: 90, w: 160, h: 90 }, + Color::YELLOW, + )); } else if hw.pad().y().pressed { os.log(LogLevel::Debug, LogSource::Hub, 0, "window Y opened".to_string()); - next_window = Some(("Red Window".to_string(), Rect { x: 160, y: 90, w: 160, h: 90 }, Color::RED)); + next_window = + Some(("Red Window".to_string(), Rect { x: 160, y: 90, w: 160, h: 90 }, Color::RED)); } if let Some((title, rect, color)) = next_window { @@ -58,4 +69,4 @@ impl PrometeuHub { ); } } -} \ No newline at end of file +} diff --git a/crates/console/prometeu-system/src/programs/prometeu_hub/window_manager.rs b/crates/console/prometeu-system/src/programs/prometeu_hub/window_manager.rs index 7748e002..eaab91ad 100644 --- a/crates/console/prometeu-system/src/programs/prometeu_hub/window_manager.rs +++ b/crates/console/prometeu-system/src/programs/prometeu_hub/window_manager.rs @@ -9,21 +9,12 @@ pub struct WindowManager { impl WindowManager { pub fn new() -> Self { - Self { - windows: Vec::new(), - focused: None, - } + Self { windows: Vec::new(), focused: None } } pub fn add_window(&mut self, title: String, viewport: Rect, color: Color) -> WindowId { let id = WindowId(self.windows.len() as u32); - let window = Window { - id, - viewport, - has_focus: false, - title, - color, - }; + let window = Window { id, viewport, has_focus: false, title, color }; self.windows.push(window); id } @@ -55,8 +46,13 @@ mod tests { #[test] fn test_window_manager_focus() { let mut wm = WindowManager::new(); - let id1 = wm.add_window("Window 1".to_string(), Rect { x: 0, y: 0, w: 10, h: 10 }, Color::WHITE); - let id2 = wm.add_window("Window 2".to_string(), Rect { x: 10, y: 10, w: 10, h: 10 }, Color::WHITE); + let id1 = + wm.add_window("Window 1".to_string(), Rect { x: 0, y: 0, w: 10, h: 10 }, Color::WHITE); + let id2 = wm.add_window( + "Window 2".to_string(), + Rect { x: 10, y: 10, w: 10, h: 10 }, + Color::WHITE, + ); assert_eq!(wm.windows.len(), 2); assert_eq!(wm.focused, None); @@ -75,7 +71,8 @@ mod tests { #[test] fn test_window_manager_remove_window() { let mut wm = WindowManager::new(); - let id = wm.add_window("Window".to_string(), Rect { x: 0, y: 0, w: 10, h: 10 }, Color::WHITE); + let id = + wm.add_window("Window".to_string(), Rect { x: 0, y: 0, w: 10, h: 10 }, Color::WHITE); wm.set_focus(id); assert_eq!(wm.focused, Some(id)); @@ -83,4 +80,4 @@ mod tests { assert_eq!(wm.windows.len(), 0); assert_eq!(wm.focused, None); } -} \ No newline at end of file +} diff --git a/crates/console/prometeu-system/src/services/fs/fs_backend.rs b/crates/console/prometeu-system/src/services/fs/fs_backend.rs index 00257b0d..55911244 100644 --- a/crates/console/prometeu-system/src/services/fs/fs_backend.rs +++ b/crates/console/prometeu-system/src/services/fs/fs_backend.rs @@ -8,5 +8,7 @@ pub trait FsBackend: Send + Sync { fn write_file(&mut self, path: &str, data: &[u8]) -> Result<(), FsError>; fn delete(&mut self, path: &str) -> Result<(), FsError>; fn exists(&self, path: &str) -> bool; - fn is_healthy(&self) -> bool { true } + fn is_healthy(&self) -> bool { + true + } } diff --git a/crates/console/prometeu-system/src/services/fs/mod.rs b/crates/console/prometeu-system/src/services/fs/mod.rs index 119667a6..dcb16257 100644 --- a/crates/console/prometeu-system/src/services/fs/mod.rs +++ b/crates/console/prometeu-system/src/services/fs/mod.rs @@ -1,7 +1,7 @@ +mod fs_backend; +mod fs_entry; mod fs_error; mod fs_state; -mod fs_entry; -mod fs_backend; mod virtual_fs; pub use fs_backend::FsBackend; diff --git a/crates/console/prometeu-system/src/services/fs/virtual_fs.rs b/crates/console/prometeu-system/src/services/fs/virtual_fs.rs index 87d7bb2a..c78552d5 100644 --- a/crates/console/prometeu-system/src/services/fs/virtual_fs.rs +++ b/crates/console/prometeu-system/src/services/fs/virtual_fs.rs @@ -1,12 +1,12 @@ use crate::fs::{FsBackend, FsEntry, FsError}; /// Virtual Filesystem (VFS) interface for Prometeu. -/// +/// /// The VFS provides a sandboxed, unified path interface for user applications. -/// Instead of interacting directly with the host's disk, the VM uses -/// normalized paths (e.g., `/user/save.dat`). -/// -/// The actual storage is provided by an `FsBackend`, which can be a real +/// Instead of interacting directly with the host's disk, the VM uses +/// normalized paths (e.g., `/user/save.dat`). +/// +/// The actual storage is provided by an `FsBackend`, which can be a real /// directory on disk, an in-memory map, or even a network resource. pub struct VirtualFS { /// The active storage implementation. @@ -88,22 +88,21 @@ mod tests { impl MockBackend { fn new() -> Self { - Self { - files: HashMap::new(), - healthy: true, - } + Self { files: HashMap::new(), healthy: true } } } impl FsBackend for MockBackend { - fn mount(&mut self) -> Result<(), FsError> { Ok(()) } + fn mount(&mut self) -> Result<(), FsError> { + Ok(()) + } fn unmount(&mut self) {} fn list_dir(&self, _path: &str) -> Result, FsError> { - Ok(self.files.keys().map(|name| FsEntry { - name: name.clone(), - is_dir: false, - size: 0, - }).collect()) + Ok(self + .files + .keys() + .map(|name| FsEntry { name: name.clone(), is_dir: false, size: 0 }) + .collect()) } fn read_file(&self, path: &str) -> Result, FsError> { self.files.get(path).cloned().ok_or(FsError::NotFound) @@ -128,18 +127,18 @@ mod tests { fn test_virtual_fs_operations() { let mut vfs = VirtualFS::new(); let backend = MockBackend::new(); - + vfs.mount(Box::new(backend)).unwrap(); - + let test_file = "/user/test.txt"; let content = b"hello world"; - + vfs.write_file(test_file, content).unwrap(); assert!(vfs.exists(test_file)); - + let read_content = vfs.read_file(test_file).unwrap(); assert_eq!(read_content, content); - + vfs.delete(test_file).unwrap(); assert!(!vfs.exists(test_file)); } @@ -149,7 +148,7 @@ mod tests { let mut vfs = VirtualFS::new(); let mut backend = MockBackend::new(); backend.healthy = false; - + vfs.mount(Box::new(backend)).unwrap(); assert!(!vfs.is_healthy()); } diff --git a/crates/console/prometeu-system/src/services/mod.rs b/crates/console/prometeu-system/src/services/mod.rs index 57f1feea..d521fbd7 100644 --- a/crates/console/prometeu-system/src/services/mod.rs +++ b/crates/console/prometeu-system/src/services/mod.rs @@ -1 +1 @@ -pub mod fs; \ No newline at end of file +pub mod fs; diff --git a/crates/console/prometeu-system/src/virtual_machine_runtime.rs b/crates/console/prometeu-system/src/virtual_machine_runtime.rs index 0dae3087..1b2d665c 100644 --- a/crates/console/prometeu-system/src/virtual_machine_runtime.rs +++ b/crates/console/prometeu-system/src/virtual_machine_runtime.rs @@ -1,21 +1,20 @@ -use prometeu_hal::syscalls::Syscall; use crate::fs::{FsBackend, FsState, VirtualFS}; -use prometeu_hal::{HardwareBridge, InputSignals}; -use prometeu_hal::log::{LogLevel, LogService, LogSource}; -use prometeu_hal::telemetry::{CertificationConfig, Certifier, TelemetryFrame}; -use std::collections::HashMap; -use std::time::Instant; -use prometeu_bytecode::{Value, TRAP_INVALID_SYSCALL, TRAP_OOB, TRAP_TYPE}; -use prometeu_hal::{expect_bool, expect_int, HostContext, HostReturn, NativeInterface, SyscallId}; +use prometeu_bytecode::{TRAP_INVALID_SYSCALL, TRAP_OOB, TRAP_TYPE, Value}; use prometeu_hal::asset::{BankType, LoadStatus, SlotRef}; use prometeu_hal::button::Button; use prometeu_hal::cartridge::{AppMode, Cartridge}; use prometeu_hal::color::Color; +use prometeu_hal::log::{LogLevel, LogService, LogSource}; use prometeu_hal::sprite::Sprite; +use prometeu_hal::syscalls::Syscall; +use prometeu_hal::telemetry::{CertificationConfig, Certifier, TelemetryFrame}; use prometeu_hal::tile::Tile; use prometeu_hal::vm_fault::VmFault; +use prometeu_hal::{HardwareBridge, InputSignals}; +use prometeu_hal::{HostContext, HostReturn, NativeInterface, SyscallId, expect_bool, expect_int}; use prometeu_vm::{LogicalFrameEndingReason, VirtualMachine}; - +use std::collections::HashMap; +use std::time::Instant; pub struct VirtualMachineRuntime { /// Host Tick Index: Incremented on every host hardware update (usually 60Hz). @@ -128,7 +127,12 @@ impl VirtualMachineRuntime { match self.fs.mount(backend) { Ok(_) => { self.fs_state = FsState::Mounted; - self.log(LogLevel::Info, LogSource::Fs, 0, "Filesystem mounted successfully".to_string()); + self.log( + LogLevel::Info, + LogSource::Fs, + 0, + "Filesystem mounted successfully".to_string(), + ); } Err(e) => { let err_msg = format!("Failed to mount filesystem: {:?}", e); @@ -146,7 +150,12 @@ impl VirtualMachineRuntime { fn update_fs(&mut self) { if self.fs_state == FsState::Mounted { if !self.fs.is_healthy() { - self.log(LogLevel::Error, LogSource::Fs, 0, "Filesystem became unhealthy, unmounting".to_string()); + self.log( + LogLevel::Error, + LogSource::Fs, + 0, + "Filesystem became unhealthy, unmounting".to_string(), + ); self.unmount_fs(); } } @@ -173,7 +182,12 @@ impl VirtualMachineRuntime { self.current_entrypoint = cartridge.entrypoint.clone(); } Err(e) => { - self.log(LogLevel::Error, LogSource::Vm, 0, format!("Failed to initialize VM: {:?}", e)); + self.log( + LogLevel::Error, + LogSource::Vm, + 0, + format!("Failed to initialize VM: {:?}", e), + ); // Fail fast: no program is installed, no app id is switched. // We don't update current_app_id or other fields. } @@ -181,7 +195,11 @@ impl VirtualMachineRuntime { } /// Executes a single VM instruction (Debug). - pub fn debug_step_instruction(&mut self, vm: &mut VirtualMachine, hw: &mut dyn HardwareBridge) -> Option { + pub fn debug_step_instruction( + &mut self, + vm: &mut VirtualMachine, + hw: &mut dyn HardwareBridge, + ) -> Option { let mut ctx = HostContext::new(Some(hw)); match vm.step(self, &mut ctx) { Ok(_) => None, @@ -198,7 +216,12 @@ impl VirtualMachineRuntime { /// This method is responsible for managing the logical frame lifecycle. /// A single host tick might execute a full logical frame, part of it, /// or multiple frames depending on the configured slices. - pub fn tick(&mut self, vm: &mut VirtualMachine, signals: &InputSignals, hw: &mut dyn HardwareBridge) -> Option { + pub fn tick( + &mut self, + vm: &mut VirtualMachine, + signals: &InputSignals, + hw: &mut dyn HardwareBridge, + ) -> Option { let start = Instant::now(); self.tick_index += 1; @@ -228,7 +251,11 @@ impl VirtualMachineRuntime { // Reset telemetry for the new logical frame self.telemetry_current = TelemetryFrame { frame_index: self.logical_frame_index, - cycles_budget: self.certifier.config.cycles_budget_per_frame.unwrap_or(Self::CYCLES_PER_LOGICAL_FRAME), + cycles_budget: self + .certifier + .config + .cycles_budget_per_frame + .unwrap_or(Self::CYCLES_PER_LOGICAL_FRAME), ..Default::default() }; } @@ -247,7 +274,8 @@ impl VirtualMachineRuntime { match run_result { Ok(run) => { - self.logical_frame_remaining_cycles = self.logical_frame_remaining_cycles.saturating_sub(run.cycles_used); + self.logical_frame_remaining_cycles = + self.logical_frame_remaining_cycles.saturating_sub(run.cycles_used); // Accumulate metrics for telemetry and certification self.telemetry_current.cycles_used += run.cycles_used; @@ -257,7 +285,12 @@ impl VirtualMachineRuntime { if run.reason == LogicalFrameEndingReason::Breakpoint { self.paused = true; self.debug_step_request = false; - self.log(LogLevel::Info, LogSource::Vm, 0xDEB1, format!("Breakpoint hit at PC 0x{:X}", vm.pc)); + self.log( + LogLevel::Info, + LogSource::Vm, + 0xDEB1, + format!("Breakpoint hit at PC 0x{:X}", vm.pc), + ); } // Handle Panics @@ -268,18 +301,24 @@ impl VirtualMachineRuntime { } // 4. Frame Finalization (FRAME_SYNC reached or Entrypoint returned) - if run.reason == LogicalFrameEndingReason::FrameSync || - run.reason == LogicalFrameEndingReason::EndOfRom { + if run.reason == LogicalFrameEndingReason::FrameSync + || run.reason == LogicalFrameEndingReason::EndOfRom + { // All drawing commands for this frame are now complete. // Finalize the framebuffer. hw.gfx_mut().render_all(); // Finalize frame telemetry - self.telemetry_current.host_cpu_time_us = start.elapsed().as_micros() as u64; + self.telemetry_current.host_cpu_time_us = + start.elapsed().as_micros() as u64; // Evaluate CAP (Execution Budget Compliance) let ts_ms = self.boot_time.elapsed().as_millis() as u64; - self.telemetry_current.violations = self.certifier.evaluate(&self.telemetry_current, &mut self.log_service, ts_ms) as u32; + self.telemetry_current.violations = self.certifier.evaluate( + &self.telemetry_current, + &mut self.log_service, + ts_ms, + ) as u32; // Latch telemetry for the Host/Debugger to read. self.telemetry_last = self.telemetry_current; @@ -324,7 +363,9 @@ impl VirtualMachineRuntime { self.telemetry_current.audio_slots_occupied = audio_stats.slots_occupied as u32; // If the frame ended exactly in this tick, we update the final real time in the latch. - if !self.logical_frame_active && self.telemetry_last.frame_index == self.logical_frame_index.wrapping_sub(1) { + if !self.logical_frame_active + && self.telemetry_last.frame_index == self.logical_frame_index.wrapping_sub(1) + { self.telemetry_last.host_cpu_time_us = self.last_frame_cpu_time_us; self.telemetry_last.cycles_budget = self.telemetry_current.cycles_budget; self.telemetry_last.gfx_used_bytes = self.telemetry_current.gfx_used_bytes; @@ -343,7 +384,6 @@ impl VirtualMachineRuntime { self.logs_written_this_frame.clear(); } - // Helper for syscalls fn syscall_log_write(&mut self, level_val: i64, tag: u16, msg: String) -> Result<(), VmFault> { let level = match level_val { @@ -361,7 +401,12 @@ impl VirtualMachineRuntime { if count >= Self::MAX_LOGS_PER_FRAME { if count == Self::MAX_LOGS_PER_FRAME { self.logs_written_this_frame.insert(app_id, count + 1); - self.log(LogLevel::Warn, LogSource::App { app_id }, 0, "App exceeded log limit per frame".to_string()); + self.log( + LogLevel::Warn, + LogSource::App { app_id }, + 0, + "App exceeded log limit per frame".to_string(), + ); } return Ok(()); } @@ -434,11 +479,17 @@ impl NativeInterface for VirtualMachineRuntime { /// - 0x5000: Logging /// /// Each syscall returns the number of virtual cycles it consumed. - fn syscall(&mut self, id: SyscallId, args: &[Value], ret: &mut HostReturn, ctx: &mut HostContext) -> Result<(), VmFault> { + fn syscall( + &mut self, + id: SyscallId, + args: &[Value], + ret: &mut HostReturn, + ctx: &mut HostContext, + ) -> Result<(), VmFault> { self.telemetry_current.syscalls += 1; - let syscall = Syscall::from_u32(id).ok_or_else(|| VmFault::Trap(TRAP_INVALID_SYSCALL, format!( - "Unknown syscall: 0x{:08X}", id - )))?; + let syscall = Syscall::from_u32(id).ok_or_else(|| { + VmFault::Trap(TRAP_INVALID_SYSCALL, format!("Unknown syscall: 0x{:08X}", id)) + })?; // Handle hardware-less syscalls first match syscall { @@ -457,7 +508,6 @@ impl NativeInterface for VirtualMachineRuntime { match syscall { // --- System Syscalls --- - Syscall::SystemHasCart => unreachable!(), Syscall::SystemRunCart => unreachable!(), @@ -535,7 +585,10 @@ impl NativeInterface for VirtualMachineRuntime { } // gfx.set_sprite(asset_name, id, x, y, tile_id, palette_id, active, flip_x, flip_y, priority) Syscall::GfxSetSprite => { - let asset_name = match args.get(0).ok_or_else(|| VmFault::Panic("Missing asset_name".into()))? { + let asset_name = match args + .get(0) + .ok_or_else(|| VmFault::Panic("Missing asset_name".into()))? + { Value::String(s) => s.clone(), _ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string asset_name".into())), }; @@ -549,7 +602,8 @@ impl NativeInterface for VirtualMachineRuntime { let flip_y = expect_bool(args, 8)?; let priority = expect_int(args, 9)? as u8; - let bank_id = hw.assets().find_slot_by_name(&asset_name, BankType::TILES).unwrap_or(0); + let bank_id = + hw.assets().find_slot_by_name(&asset_name, BankType::TILES).unwrap_or(0); if index < 512 { *hw.gfx_mut().sprite_mut(index) = Sprite { @@ -569,7 +623,10 @@ impl NativeInterface for VirtualMachineRuntime { Syscall::GfxDrawText => { let x = expect_int(args, 0)? as i32; let y = expect_int(args, 1)? as i32; - let msg = match args.get(2).ok_or_else(|| VmFault::Panic("Missing message".into()))? { + let msg = match args + .get(2) + .ok_or_else(|| VmFault::Panic("Missing message".into()))? + { Value::String(s) => s.clone(), _ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string message".into())), }; @@ -583,7 +640,10 @@ impl NativeInterface for VirtualMachineRuntime { Syscall::GfxClear565 => { let color_val = expect_int(args, 0)? as u32; if color_val > 0xFFFF { - return Err(VmFault::Trap(TRAP_OOB, "Color value out of bounds (bounded)".into())); + return Err(VmFault::Trap( + TRAP_OOB, + "Color value out of bounds (bounded)".into(), + )); } let color = Color::from_raw(color_val as u16); hw.gfx_mut().clear(color); @@ -655,9 +715,18 @@ impl NativeInterface for VirtualMachineRuntime { Syscall::InputPadSnapshot => { let pad = hw.pad(); for btn in [ - pad.up(), pad.down(), pad.left(), pad.right(), - pad.a(), pad.b(), pad.x(), pad.y(), - pad.l(), pad.r(), pad.start(), pad.select(), + pad.up(), + pad.down(), + pad.left(), + pad.right(), + pad.a(), + pad.b(), + pad.x(), + pad.y(), + pad.l(), + pad.r(), + pad.start(), + pad.select(), ] { ret.push_bool(btn.pressed); ret.push_bool(btn.released); @@ -783,7 +852,10 @@ impl NativeInterface for VirtualMachineRuntime { let voice_id = expect_int(args, 1)? as usize; let volume = expect_int(args, 2)? as u8; let pan = expect_int(args, 3)? as u8; - let pitch = match args.get(4).ok_or_else(|| VmFault::Panic("Missing pitch".into()))? { + let pitch = match args + .get(4) + .ok_or_else(|| VmFault::Panic("Missing pitch".into()))? + { Value::Float(f) => *f, Value::Int32(i) => *i as f64, Value::Int64(i) => *i as f64, @@ -791,14 +863,26 @@ impl NativeInterface for VirtualMachineRuntime { _ => return Err(VmFault::Trap(TRAP_TYPE, "Expected number for pitch".into())), }; - hw.audio_mut().play(0, sample_id as u16, voice_id, volume, pan, pitch, 0, prometeu_hal::LoopMode::Off); + hw.audio_mut().play( + 0, + sample_id as u16, + voice_id, + volume, + pan, + pitch, + 0, + prometeu_hal::LoopMode::Off, + ); ret.push_null(); Ok(()) } // audio.play(asset_name, sample_id, voice_id, volume, pan, pitch, loop_mode) Syscall::AudioPlay => { - let asset_name = match args.get(0).ok_or_else(|| VmFault::Panic("Missing asset_name".into()))? { + let asset_name = match args + .get(0) + .ok_or_else(|| VmFault::Panic("Missing asset_name".into()))? + { Value::String(s) => s.clone(), _ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string asset_name".into())), }; @@ -806,7 +890,10 @@ impl NativeInterface for VirtualMachineRuntime { let voice_id = expect_int(args, 2)? as usize; let volume = expect_int(args, 3)? as u8; let pan = expect_int(args, 4)? as u8; - let pitch = match args.get(5).ok_or_else(|| VmFault::Panic("Missing pitch".into()))? { + let pitch = match args + .get(5) + .ok_or_else(|| VmFault::Panic("Missing pitch".into()))? + { Value::Float(f) => *f, Value::Int32(i) => *i as f64, Value::Int64(i) => *i as f64, @@ -818,7 +905,8 @@ impl NativeInterface for VirtualMachineRuntime { _ => prometeu_hal::LoopMode::On, }; - let bank_id = hw.assets().find_slot_by_name(&asset_name, BankType::SOUNDS).unwrap_or(0); + let bank_id = + hw.assets().find_slot_by_name(&asset_name, BankType::SOUNDS).unwrap_or(0); hw.audio_mut().play(bank_id, sample_id, voice_id, volume, pan, pitch, 0, loop_mode); ret.push_null(); @@ -846,7 +934,10 @@ impl NativeInterface for VirtualMachineRuntime { // FS_READ(handle) -> content Syscall::FsRead => { let handle = expect_int(args, 0)? as u32; - let path = self.open_files.get(&handle).ok_or_else(|| VmFault::Panic("Invalid handle".into()))?; + let path = self + .open_files + .get(&handle) + .ok_or_else(|| VmFault::Panic("Invalid handle".into()))?; match self.fs.read_file(path) { Ok(data) => { let s = String::from_utf8_lossy(&data).into_owned(); @@ -859,11 +950,17 @@ impl NativeInterface for VirtualMachineRuntime { // FS_WRITE(handle, content) Syscall::FsWrite => { let handle = expect_int(args, 0)? as u32; - let content = match args.get(1).ok_or_else(|| VmFault::Panic("Missing content".into()))? { + let content = match args + .get(1) + .ok_or_else(|| VmFault::Panic("Missing content".into()))? + { Value::String(s) => s.as_bytes().to_vec(), _ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string content".into())), }; - let path = self.open_files.get(&handle).ok_or_else(|| VmFault::Panic("Invalid handle".into()))?; + let path = self + .open_files + .get(&handle) + .ok_or_else(|| VmFault::Panic("Invalid handle".into()))?; match self.fs.write_file(path, &content) { Ok(_) => ret.push_bool(true), Err(_) => ret.push_bool(false), @@ -919,7 +1016,10 @@ impl NativeInterface for VirtualMachineRuntime { // LOG_WRITE(level, msg) Syscall::LogWrite => { let level = expect_int(args, 0)?; - let msg = match args.get(1).ok_or_else(|| VmFault::Panic("Missing message".into()))? { + let msg = match args + .get(1) + .ok_or_else(|| VmFault::Panic("Missing message".into()))? + { Value::String(s) => s.clone(), _ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string message".into())), }; @@ -931,7 +1031,10 @@ impl NativeInterface for VirtualMachineRuntime { Syscall::LogWriteTag => { let level = expect_int(args, 0)?; let tag = expect_int(args, 1)? as u16; - let msg = match args.get(2).ok_or_else(|| VmFault::Panic("Missing message".into()))? { + let msg = match args + .get(2) + .ok_or_else(|| VmFault::Panic("Missing message".into()))? + { Value::String(s) => s.clone(), _ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string message".into())), }; @@ -942,7 +1045,10 @@ impl NativeInterface for VirtualMachineRuntime { // --- Asset Syscalls --- Syscall::AssetLoad => { - let asset_id = match args.get(0).ok_or_else(|| VmFault::Panic("Missing asset_id".into()))? { + let asset_id = match args + .get(0) + .ok_or_else(|| VmFault::Panic("Missing asset_id".into()))? + { Value::String(s) => s.clone(), _ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string asset_id".into())), }; @@ -1018,4 +1124,4 @@ impl NativeInterface for VirtualMachineRuntime { } } } -} \ No newline at end of file +} diff --git a/crates/console/prometeu-vm/Cargo.toml b/crates/console/prometeu-vm/Cargo.toml index ebc104f7..dfaf7ceb 100644 --- a/crates/console/prometeu-vm/Cargo.toml +++ b/crates/console/prometeu-vm/Cargo.toml @@ -7,3 +7,6 @@ license.workspace = true [dependencies] prometeu-bytecode = { path = "../prometeu-bytecode" } prometeu-hal = { path = "../prometeu-hal" } + +[dev-dependencies] +prometeu-test-support = { path = "../../dev/prometeu-test-support" } diff --git a/crates/console/prometeu-vm/src/call_frame.rs b/crates/console/prometeu-vm/src/call_frame.rs index 62a9fd73..ba73d78f 100644 --- a/crates/console/prometeu-vm/src/call_frame.rs +++ b/crates/console/prometeu-vm/src/call_frame.rs @@ -2,4 +2,4 @@ pub struct CallFrame { pub return_pc: u32, pub stack_base: usize, pub func_idx: usize, -} \ No newline at end of file +} diff --git a/crates/console/prometeu-vm/src/lib.rs b/crates/console/prometeu-vm/src/lib.rs index 458a23c8..7f06fb93 100644 --- a/crates/console/prometeu-vm/src/lib.rs +++ b/crates/console/prometeu-vm/src/lib.rs @@ -1,11 +1,9 @@ -mod virtual_machine; mod call_frame; -mod scope_frame; pub mod local_addressing; +mod scope_frame; pub mod verifier; +mod virtual_machine; pub mod vm_init_error; -pub use prometeu_hal::{ - HostContext, HostReturn, NativeInterface, SyscallId, -}; +pub use prometeu_hal::{HostContext, HostReturn, NativeInterface, SyscallId}; pub use virtual_machine::{BudgetReport, LogicalFrameEndingReason, VirtualMachine}; diff --git a/crates/console/prometeu-vm/src/local_addressing.rs b/crates/console/prometeu-vm/src/local_addressing.rs index a703783b..5b3248fd 100644 --- a/crates/console/prometeu-vm/src/local_addressing.rs +++ b/crates/console/prometeu-vm/src/local_addressing.rs @@ -1,6 +1,6 @@ use crate::call_frame::CallFrame; -use prometeu_bytecode::{TrapInfo, TRAP_INVALID_LOCAL}; use prometeu_bytecode::FunctionMeta; +use prometeu_bytecode::{TRAP_INVALID_LOCAL, TrapInfo}; /// Computes the absolute stack index for the start of the current frame's locals (including args). pub fn local_base(frame: &CallFrame) -> usize { @@ -14,7 +14,12 @@ pub fn local_index(frame: &CallFrame, slot: u32) -> usize { /// Validates that a local slot index is within the valid range for the function. /// Range: 0 <= slot < (param_slots + local_slots) -pub fn check_local_slot(meta: &FunctionMeta, slot: u32, opcode: u16, pc: u32) -> Result<(), TrapInfo> { +pub fn check_local_slot( + meta: &FunctionMeta, + slot: u32, + opcode: u16, + pc: u32, +) -> Result<(), TrapInfo> { let limit = meta.param_slots as u32 + meta.local_slots as u32; if slot < limit { Ok(()) diff --git a/crates/console/prometeu-vm/src/scope_frame.rs b/crates/console/prometeu-vm/src/scope_frame.rs index e0d927bb..7a5d1dd3 100644 --- a/crates/console/prometeu-vm/src/scope_frame.rs +++ b/crates/console/prometeu-vm/src/scope_frame.rs @@ -1,3 +1,3 @@ pub struct ScopeFrame { pub scope_stack_base: usize, -} \ No newline at end of file +} diff --git a/crates/console/prometeu-vm/src/verifier.rs b/crates/console/prometeu-vm/src/verifier.rs index ac7bf037..a2f8de6f 100644 --- a/crates/console/prometeu-vm/src/verifier.rs +++ b/crates/console/prometeu-vm/src/verifier.rs @@ -1,9 +1,9 @@ -use prometeu_hal::syscalls::Syscall; -use prometeu_bytecode::{decode_next, DecodeError}; +use prometeu_bytecode::FunctionMeta; use prometeu_bytecode::OpCode; use prometeu_bytecode::OpCodeSpecExt; -use prometeu_bytecode::FunctionMeta; -use prometeu_bytecode::{compute_function_layouts, FunctionLayout}; +use prometeu_bytecode::{DecodeError, decode_next}; +use prometeu_bytecode::{FunctionLayout, compute_function_layouts}; +use prometeu_hal::syscalls::Syscall; use std::collections::{HashMap, HashSet, VecDeque}; #[derive(Debug, Clone, PartialEq, Eq)] @@ -71,14 +71,23 @@ impl Verifier { while pc < func_code.len() { valid_pc.insert(pc); let instr = decode_next(pc, func_code).map_err(|e| match e { - DecodeError::UnknownOpcode { pc: _, opcode } => - VerifierError::UnknownOpcode { pc: func_start + pc, opcode }, - DecodeError::TruncatedOpcode { pc: _ } => - VerifierError::TruncatedOpcode { pc: func_start + pc }, - DecodeError::TruncatedImmediate { pc: _, opcode, need, have } => - VerifierError::TruncatedImmediate { pc: func_start + pc, opcode, need, have }, - DecodeError::ImmediateSizeMismatch { pc: _, opcode, expected, actual } => - VerifierError::TruncatedImmediate { pc: func_start + pc, opcode, need: expected, have: actual }, + DecodeError::UnknownOpcode { pc: _, opcode } => { + VerifierError::UnknownOpcode { pc: func_start + pc, opcode } + } + DecodeError::TruncatedOpcode { pc: _ } => { + VerifierError::TruncatedOpcode { pc: func_start + pc } + } + DecodeError::TruncatedImmediate { pc: _, opcode, need, have } => { + VerifierError::TruncatedImmediate { pc: func_start + pc, opcode, need, have } + } + DecodeError::ImmediateSizeMismatch { pc: _, opcode, expected, actual } => { + VerifierError::TruncatedImmediate { + pc: func_start + pc, + opcode, + need: expected, + have: actual, + } + } })?; pc = instr.next_pc; } @@ -113,9 +122,7 @@ impl Verifier { })?; (callee.param_slots, callee.return_slots) } - OpCode::Ret => { - (func.return_slots, 0) - } + OpCode::Ret => (func.return_slots, 0), OpCode::Syscall => { let id = instr.imm_u32().unwrap(); let syscall = Syscall::from_u32(id).ok_or_else(|| { @@ -127,7 +134,10 @@ impl Verifier { }; if in_height < pops { - return Err(VerifierError::StackUnderflow { pc: func_start + pc, opcode: instr.opcode }); + return Err(VerifierError::StackUnderflow { + pc: func_start + pc, + opcode: instr.opcode, + }); } let out_height = in_height - pops + pushes; @@ -135,7 +145,11 @@ impl Verifier { if instr.opcode == OpCode::Ret { if in_height != func.return_slots { - return Err(VerifierError::BadRetStackHeight { pc: func_start + pc, height: in_height, expected: func.return_slots }); + return Err(VerifierError::BadRetStackHeight { + pc: func_start + pc, + height: in_height, + expected: func.return_slots, + }); } } @@ -143,7 +157,8 @@ impl Verifier { if spec.is_branch { // Canonical contract: branch immediate is RELATIVE to function start. let target_rel = instr.imm_u32().unwrap() as usize; - let func_end_abs = layouts.get(func_idx).map(|l| l.end).unwrap_or_else(|| code.len()); + let func_end_abs = + layouts.get(func_idx).map(|l| l.end).unwrap_or_else(|| code.len()); let func_len = func_end_abs - func_start; if target_rel > func_len { @@ -168,23 +183,38 @@ impl Verifier { target_abs_expected, is_boundary_target_rel ); - return Err(VerifierError::InvalidJumpTarget { pc: pc_abs, target: target_abs_expected }); + return Err(VerifierError::InvalidJumpTarget { + pc: pc_abs, + target: target_abs_expected, + }); } if target_rel == func_len { // salto para o fim da funรงรฃo if out_height != func.return_slots { - return Err(VerifierError::BadRetStackHeight { pc: func_start + pc, height: out_height, expected: func.return_slots }); + return Err(VerifierError::BadRetStackHeight { + pc: func_start + pc, + height: out_height, + expected: func.return_slots, + }); } // caminho termina aqui } else { if !valid_pc.contains(&target_rel) { - return Err(VerifierError::JumpToMidInstruction { pc: func_start + pc, target: func_start + target_rel }); + return Err(VerifierError::JumpToMidInstruction { + pc: func_start + pc, + target: func_start + target_rel, + }); } if let Some(&existing_height) = stack_height_in.get(&target_rel) { if existing_height != out_height { - return Err(VerifierError::StackMismatchJoin { pc: func_start + pc, target: func_start + target_rel, height_in: out_height, height_target: existing_height }); + return Err(VerifierError::StackMismatchJoin { + pc: func_start + pc, + target: func_start + target_rel, + height_in: out_height, + height_target: existing_height, + }); } } else { stack_height_in.insert(target_rel, out_height); @@ -199,7 +229,12 @@ impl Verifier { if next_pc < func_len { if let Some(&existing_height) = stack_height_in.get(&next_pc) { if existing_height != out_height { - return Err(VerifierError::StackMismatchJoin { pc: func_start + pc, target: func_start + next_pc, height_in: out_height, height_target: existing_height }); + return Err(VerifierError::StackMismatchJoin { + pc: func_start + pc, + target: func_start + next_pc, + height_in: out_height, + height_target: existing_height, + }); } } else { stack_height_in.insert(next_pc, out_height); @@ -221,18 +256,15 @@ mod tests { fn test_verifier_underflow() { // OpCode::Add (2 bytes) let code = vec![OpCode::Add as u8, 0x00]; - let functions = vec![FunctionMeta { - code_offset: 0, - code_len: 2, - ..Default::default() - }]; + let functions = vec![FunctionMeta { code_offset: 0, code_len: 2, ..Default::default() }]; let res = Verifier::verify(&code, &functions); assert_eq!(res, Err(VerifierError::StackUnderflow { pc: 0, opcode: OpCode::Add })); } #[test] fn test_verifier_dup_underflow() { - let code = vec![(OpCode::Dup as u16).to_le_bytes()[0], (OpCode::Dup as u16).to_le_bytes()[1]]; + let code = + vec![(OpCode::Dup as u16).to_le_bytes()[0], (OpCode::Dup as u16).to_le_bytes()[1]]; let functions = vec![FunctionMeta { code_offset: 0, code_len: 2, ..Default::default() }]; let res = Verifier::verify(&code, &functions); assert_eq!(res, Err(VerifierError::StackUnderflow { pc: 0, opcode: OpCode::Dup })); @@ -243,11 +275,7 @@ mod tests { // Jmp (2 bytes) + 100u32 (4 bytes) let mut code = vec![OpCode::Jmp as u8, 0x00]; code.extend_from_slice(&100u32.to_le_bytes()); - let functions = vec![FunctionMeta { - code_offset: 0, - code_len: 6, - ..Default::default() - }]; + let functions = vec![FunctionMeta { code_offset: 0, code_len: 6, ..Default::default() }]; let res = Verifier::verify(&code, &functions); assert_eq!(res, Err(VerifierError::InvalidJumpTarget { pc: 0, target: 100 })); } @@ -261,12 +289,8 @@ mod tests { code.push(OpCode::Jmp as u8); code.push(0x00); code.extend_from_slice(&1u32.to_le_bytes()); - - let functions = vec![FunctionMeta { - code_offset: 0, - code_len: 12, - ..Default::default() - }]; + + let functions = vec![FunctionMeta { code_offset: 0, code_len: 12, ..Default::default() }]; let res = Verifier::verify(&code, &functions); assert_eq!(res, Err(VerifierError::JumpToMidInstruction { pc: 6, target: 1 })); } @@ -295,11 +319,7 @@ mod tests { #[test] fn test_verifier_truncation_opcode() { let code = vec![OpCode::PushI32 as u8]; // Truncated u16 opcode - let functions = vec![FunctionMeta { - code_offset: 0, - code_len: 1, - ..Default::default() - }]; + let functions = vec![FunctionMeta { code_offset: 0, code_len: 1, ..Default::default() }]; let res = Verifier::verify(&code, &functions); assert_eq!(res, Err(VerifierError::TruncatedOpcode { pc: 0 })); } @@ -308,13 +328,17 @@ mod tests { fn test_verifier_truncation_immediate() { let mut code = vec![OpCode::PushI32 as u8, 0x00]; code.push(0x42); // Only 1 byte of 4-byte immediate - let functions = vec![FunctionMeta { - code_offset: 0, - code_len: 3, - ..Default::default() - }]; + let functions = vec![FunctionMeta { code_offset: 0, code_len: 3, ..Default::default() }]; let res = Verifier::verify(&code, &functions); - assert_eq!(res, Err(VerifierError::TruncatedImmediate { pc: 0, opcode: OpCode::PushI32, need: 4, have: 1 })); + assert_eq!( + res, + Err(VerifierError::TruncatedImmediate { + pc: 0, + opcode: OpCode::PushI32, + need: 4, + have: 1 + }) + ); } #[test] @@ -326,26 +350,41 @@ mod tests { // 15: PushI32 1 // 21: Jmp 27 // 27: Nop - + let mut code = Vec::new(); - code.push(OpCode::PushBool as u8); code.push(0x00); code.push(1); // 0: PushBool (3 bytes) - code.push(OpCode::JmpIfTrue as u8); code.push(0x00); code.extend_from_slice(&15u32.to_le_bytes()); // 3: JmpIfTrue (6 bytes) - code.push(OpCode::Jmp as u8); code.push(0x00); code.extend_from_slice(&27u32.to_le_bytes()); // 9: Jmp (6 bytes) - code.push(OpCode::PushI32 as u8); code.push(0x00); code.extend_from_slice(&1u32.to_le_bytes()); // 15: PushI32 (6 bytes) - code.push(OpCode::Jmp as u8); code.push(0x00); code.extend_from_slice(&27u32.to_le_bytes()); // 21: Jmp (6 bytes) - code.push(OpCode::Nop as u8); code.push(0x00); // 27: Nop (2 bytes) - - let functions = vec![FunctionMeta { - code_offset: 0, - code_len: 29, - ..Default::default() - }]; + code.push(OpCode::PushBool as u8); + code.push(0x00); + code.push(1); // 0: PushBool (3 bytes) + code.push(OpCode::JmpIfTrue as u8); + code.push(0x00); + code.extend_from_slice(&15u32.to_le_bytes()); // 3: JmpIfTrue (6 bytes) + code.push(OpCode::Jmp as u8); + code.push(0x00); + code.extend_from_slice(&27u32.to_le_bytes()); // 9: Jmp (6 bytes) + code.push(OpCode::PushI32 as u8); + code.push(0x00); + code.extend_from_slice(&1u32.to_le_bytes()); // 15: PushI32 (6 bytes) + code.push(OpCode::Jmp as u8); + code.push(0x00); + code.extend_from_slice(&27u32.to_le_bytes()); // 21: Jmp (6 bytes) + code.push(OpCode::Nop as u8); + code.push(0x00); // 27: Nop (2 bytes) + + let functions = vec![FunctionMeta { code_offset: 0, code_len: 29, ..Default::default() }]; let res = Verifier::verify(&code, &functions); // Path 0->3->9->27: height 1-1+0 = 0. // Path 0->3->15->21->27: height 1-1+1 = 1. // Mismatch at 27: 0 vs 1. - - assert_eq!(res, Err(VerifierError::StackMismatchJoin { pc: 21, target: 27, height_in: 1, height_target: 0 })); + + assert_eq!( + res, + Err(VerifierError::StackMismatchJoin { + pc: 21, + target: 27, + height_in: 1, + height_target: 0 + }) + ); } #[test] @@ -356,7 +395,7 @@ mod tests { code.extend_from_slice(&1u32.to_le_bytes()); code.push(OpCode::Ret as u8); code.push(0x00); - + let functions = vec![FunctionMeta { code_offset: 0, code_len: 8, @@ -374,11 +413,17 @@ mod tests { // Add // Ret let mut code = Vec::new(); - code.push(OpCode::PushI32 as u8); code.push(0x00); code.extend_from_slice(&1u32.to_le_bytes()); - code.push(OpCode::PushI32 as u8); code.push(0x00); code.extend_from_slice(&2u32.to_le_bytes()); - code.push(OpCode::Add as u8); code.push(0x00); - code.push(OpCode::Ret as u8); code.push(0x00); - + code.push(OpCode::PushI32 as u8); + code.push(0x00); + code.extend_from_slice(&1u32.to_le_bytes()); + code.push(OpCode::PushI32 as u8); + code.push(0x00); + code.extend_from_slice(&2u32.to_le_bytes()); + code.push(OpCode::Add as u8); + code.push(0x00); + code.push(OpCode::Ret as u8); + code.push(0x00); + let functions = vec![FunctionMeta { code_offset: 0, code_len: 16, @@ -392,14 +437,11 @@ mod tests { #[test] fn test_verifier_invalid_syscall_id() { let mut code = Vec::new(); - code.push(OpCode::Syscall as u8); code.push(0x00); + code.push(OpCode::Syscall as u8); + code.push(0x00); code.extend_from_slice(&0xDEADBEEFu32.to_le_bytes()); // Unknown ID - - let functions = vec![FunctionMeta { - code_offset: 0, - code_len: 6, - ..Default::default() - }]; + + let functions = vec![FunctionMeta { code_offset: 0, code_len: 6, ..Default::default() }]; let res = Verifier::verify(&code, &functions); assert_eq!(res, Err(VerifierError::InvalidSyscallId { pc: 0, id: 0xDEADBEEF })); } diff --git a/crates/console/prometeu-vm/src/virtual_machine.rs b/crates/console/prometeu-vm/src/virtual_machine.rs index 86495da9..e6d0e419 100644 --- a/crates/console/prometeu-vm/src/virtual_machine.rs +++ b/crates/console/prometeu-vm/src/virtual_machine.rs @@ -3,10 +3,13 @@ use crate::scope_frame::ScopeFrame; use crate::verifier::Verifier; use crate::vm_init_error::VmInitError; use crate::{HostContext, NativeInterface}; -use prometeu_bytecode::{TrapInfo, TRAP_BAD_RET_SLOTS, TRAP_DEAD_GATE, TRAP_DIV_ZERO, TRAP_INVALID_FUNC, TRAP_INVALID_GATE, TRAP_INVALID_SYSCALL, TRAP_OOB, TRAP_STACK_UNDERFLOW, TRAP_TYPE}; use prometeu_bytecode::OpCode; use prometeu_bytecode::ProgramImage; use prometeu_bytecode::Value; +use prometeu_bytecode::{ + TRAP_BAD_RET_SLOTS, TRAP_DEAD_GATE, TRAP_DIV_ZERO, TRAP_INVALID_FUNC, TRAP_INVALID_GATE, + TRAP_INVALID_SYSCALL, TRAP_OOB, TRAP_STACK_UNDERFLOW, TRAP_TYPE, TrapInfo, +}; use prometeu_hal::vm_fault::VmFault; /// Reason why the Virtual Machine stopped execution during a specific run. @@ -54,7 +57,7 @@ pub struct BudgetReport { /// The PVM (PROMETEU Virtual Machine). /// -/// A deterministic, stack-based virtual machine designed for game logic and +/// A deterministic, stack-based virtual machine designed for game logic and /// educational simulation. The PVM executes bytecode compiled from TypeScript/JS /// and interacts with virtual hardware through a specialized instruction set. /// @@ -83,7 +86,7 @@ pub struct VirtualMachine { /// The loaded executable (Bytecode + Constant Pool), that is the ROM translated. pub program: ProgramImage, /// Heap Memory: Dynamic allocation pool. - pub heap: Vec, + pub heap: Vec, /// Gate Pool: indirection table for heap objects. Value::Gate carries an index into this pool. pub gate_pool: Vec, /// Total virtual cycles consumed since the VM started. @@ -126,7 +129,13 @@ impl VirtualMachine { call_stack: Vec::new(), scope_stack: Vec::new(), globals: Vec::new(), - program: ProgramImage::new(rom, constant_pool, vec![], None, std::collections::HashMap::new()), + program: ProgramImage::new( + rom, + constant_pool, + vec![], + None, + std::collections::HashMap::new(), + ), heap: Vec::new(), gate_pool: Vec::new(), cycles: 0, @@ -137,8 +146,12 @@ impl VirtualMachine { /// Resets the VM state and loads a new program. /// This is typically called by the Firmware when starting a new App/Cartridge. - pub fn initialize(&mut self, program_bytes: Vec, entrypoint: &str) -> Result<(), VmInitError> { - // Fail fast: reset state upfront. If we return early with an error, + pub fn initialize( + &mut self, + program_bytes: Vec, + entrypoint: &str, + ) -> Result<(), VmInitError> { + // Fail fast: reset state upfront. If we return early with an error, // the VM is left in a "halted and empty" state. self.program = ProgramImage::default(); self.pc = 0; @@ -169,7 +182,9 @@ impl VirtualMachine { program } - Err(prometeu_bytecode::LoadError::InvalidVersion) => return Err(VmInitError::UnsupportedFormat), + Err(prometeu_bytecode::LoadError::InvalidVersion) => { + return Err(VmInitError::UnsupportedFormat); + } Err(e) => { return Err(VmInitError::ImageLoadFailed(e)); } @@ -182,13 +197,17 @@ impl VirtualMachine { let pc = if entrypoint.is_empty() { program.functions.get(0).map(|f| f.code_offset as usize).unwrap_or(0) } else if let Ok(func_idx) = entrypoint.parse::() { - program.functions.get(func_idx) + program + .functions + .get(func_idx) .map(|f| f.code_offset as usize) .ok_or(VmInitError::EntrypointNotFound)? } else { // Try to resolve as a symbol name from the exports map if let Some(&func_idx) = program.exports.get(entrypoint) { - program.functions.get(func_idx as usize) + program + .functions + .get(func_idx as usize) .map(|f| f.code_offset as usize) .ok_or(VmInitError::EntrypointNotFound)? } else { @@ -204,16 +223,14 @@ impl VirtualMachine { Ok(()) } - /// Prepares the VM to execute a specific entrypoint by setting the PC and + /// 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) { let func_idx = if let Ok(idx) = entrypoint.parse::() { idx } else { // Try to resolve as a symbol name - self.program.exports.get(entrypoint) - .map(|&idx| idx as usize) - .ok_or(()).unwrap_or(0) // Default to 0 if not found + self.program.exports.get(entrypoint).map(|&idx| idx as usize).ok_or(()).unwrap_or(0) // Default to 0 if not found }; let callee = self.program.functions.get(func_idx).cloned().unwrap_or_default(); @@ -221,9 +238,9 @@ impl VirtualMachine { self.pc = addr; self.halted = false; - + // Pushing a sentinel frame so RET works at the top level. - // The return address is set to the end of ROM, which will naturally + // The return address is set to the end of ROM, which will naturally // cause the VM to stop after returning from the entrypoint. self.operand_stack.clear(); self.call_stack.clear(); @@ -270,7 +287,7 @@ impl VirtualMachine { && self.pc < self.program.rom.len() { // Debugger support: stop before executing an instruction if there's a breakpoint. - // Note: we skip the check for the very first step of a slice to avoid + // Note: we skip the check for the very first step of a slice to avoid // getting stuck on the same breakpoint repeatedly. if steps_executed > 0 && self.breakpoints.contains(&self.pc) { ending_reason = Some(LogicalFrameEndingReason::Breakpoint); @@ -311,7 +328,10 @@ impl VirtualMachine { // Integrity check: ensure real progress is being made to avoid infinite loops // caused by zero-cycle instructions or stuck PC. if self.pc == pc_before && self.cycles == cycles_before && !self.halted { - ending_reason = Some(LogicalFrameEndingReason::Panic(format!("VM stuck at PC 0x{:08X}", self.pc))); + ending_reason = Some(LogicalFrameEndingReason::Panic(format!( + "VM stuck at PC 0x{:08X}", + self.pc + ))); break; } } @@ -339,10 +359,7 @@ impl VirtualMachine { if self.pc + 2 > self.program.rom.len() { return Err("Unexpected end of ROM".into()); } - let bytes = [ - self.program.rom[self.pc], - self.program.rom[self.pc + 1], - ]; + let bytes = [self.program.rom[self.pc], self.program.rom[self.pc + 1]]; Ok(u16::from_le_bytes(bytes)) } @@ -352,7 +369,11 @@ impl VirtualMachine { /// 1. Fetch: Read the opcode from memory. /// 2. Decode: Identify what operation to perform. /// 3. Execute: Perform the operation, updating stacks, memory, or calling peripherals. - pub fn step(&mut self, native: &mut dyn NativeInterface, ctx: &mut HostContext) -> Result<(), LogicalFrameEndingReason> { + pub fn step( + &mut self, + native: &mut dyn NativeInterface, + ctx: &mut HostContext, + ) -> Result<(), LogicalFrameEndingReason> { if self.halted || self.pc >= self.program.rom.len() { return Ok(()); } @@ -362,7 +383,7 @@ impl VirtualMachine { // Fetch & Decode let instr = prometeu_bytecode::decode_next(self.pc, &self.program.rom) .map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))?; - + let opcode = instr.opcode; self.pc = instr.next_pc; @@ -373,35 +394,66 @@ impl VirtualMachine { self.halted = true; } OpCode::Jmp => { - let target = instr.imm_u32().map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))? as usize; - let func_start = self.call_stack.last().map(|f| self.program.functions[f.func_idx].code_offset as usize).unwrap_or(0); + let target = instr + .imm_u32() + .map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))? + as usize; + let func_start = self + .call_stack + .last() + .map(|f| self.program.functions[f.func_idx].code_offset as usize) + .unwrap_or(0); self.pc = func_start + target; } OpCode::JmpIfFalse => { - let target = instr.imm_u32().map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))? as usize; + let target = instr + .imm_u32() + .map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))? + as usize; let val = self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?; match val { Value::Boolean(false) => { - let func_start = self.call_stack.last().map(|f| self.program.functions[f.func_idx].code_offset as usize).unwrap_or(0); + let func_start = self + .call_stack + .last() + .map(|f| self.program.functions[f.func_idx].code_offset as usize) + .unwrap_or(0); self.pc = func_start + target; } Value::Boolean(true) => {} _ => { - return Err(self.trap(TRAP_TYPE, opcode as u16, format!("Expected boolean for JMP_IF_FALSE, got {:?}", val), start_pc as u32)); + return Err(self.trap( + TRAP_TYPE, + opcode as u16, + format!("Expected boolean for JMP_IF_FALSE, got {:?}", val), + start_pc as u32, + )); } } } OpCode::JmpIfTrue => { - let target = instr.imm_u32().map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))? as usize; + let target = instr + .imm_u32() + .map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))? + as usize; let val = self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?; match val { Value::Boolean(true) => { - let func_start = self.call_stack.last().map(|f| self.program.functions[f.func_idx].code_offset as usize).unwrap_or(0); + let func_start = self + .call_stack + .last() + .map(|f| self.program.functions[f.func_idx].code_offset as usize) + .unwrap_or(0); self.pc = func_start + target; } Value::Boolean(false) => {} _ => { - return Err(self.trap(TRAP_TYPE, opcode as u16, format!("Expected boolean for JMP_IF_TRUE, got {:?}", val), start_pc as u32)); + return Err(self.trap( + TRAP_TYPE, + opcode as u16, + format!("Expected boolean for JMP_IF_TRUE, got {:?}", val), + start_pc as u32, + )); } } } @@ -409,38 +461,60 @@ impl VirtualMachine { // Handled in run_budget for interruption } OpCode::PushConst => { - let idx = instr.imm_u32().map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))? as usize; - let val = self.program.constant_pool.get(idx).cloned().ok_or_else(|| LogicalFrameEndingReason::Panic("Invalid constant index".into()))?; + let idx = instr + .imm_u32() + .map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))? + as usize; + let val = self.program.constant_pool.get(idx).cloned().ok_or_else(|| { + LogicalFrameEndingReason::Panic("Invalid constant index".into()) + })?; self.push(val); } OpCode::PushI64 => { - let val = instr.imm_i64().map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))?; + let val = instr + .imm_i64() + .map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))?; self.push(Value::Int64(val)); } OpCode::PushI32 => { - let val = instr.imm_i32().map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))?; + let val = instr + .imm_i32() + .map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))?; self.push(Value::Int32(val)); } OpCode::PushBounded => { - let val = instr.imm_u32().map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))?; + let val = instr + .imm_u32() + .map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))?; if val > 0xFFFF { - return Err(self.trap(TRAP_OOB, opcode as u16, format!("Bounded value overflow: {} > 0xFFFF", val), start_pc as u32)); + return Err(self.trap( + TRAP_OOB, + opcode as u16, + format!("Bounded value overflow: {} > 0xFFFF", val), + start_pc as u32, + )); } self.push(Value::Bounded(val)); } OpCode::PushF64 => { - let val = instr.imm_f64().map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))?; + let val = instr + .imm_f64() + .map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))?; self.push(Value::Float(val)); } OpCode::PushBool => { - let val = instr.imm_u8().map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))?; + let val = instr + .imm_u8() + .map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))?; self.push(Value::Boolean(val != 0)); } OpCode::Pop => { self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?; } OpCode::PopN => { - let n = instr.imm_u32().map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))?; + let n = instr + .imm_u32() + .map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))?; for _ in 0..n { self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?; } @@ -461,7 +535,9 @@ impl VirtualMachine { } (Value::Int32(a), Value::Int32(b)) => Ok(Value::Int32(a.wrapping_add(*b))), (Value::Int64(a), Value::Int64(b)) => Ok(Value::Int64(a.wrapping_add(*b))), - (Value::Int32(a), Value::Int64(b)) => Ok(Value::Int64((*a as i64).wrapping_add(*b))), + (Value::Int32(a), Value::Int64(b)) => { + Ok(Value::Int64((*a as i64).wrapping_add(*b))) + } (Value::Int64(a), Value::Int32(b)) => Ok(Value::Int64(a.wrapping_add(*b as i64))), (Value::Float(a), Value::Float(b)) => Ok(Value::Float(a + b)), (Value::Int32(a), Value::Float(b)) => Ok(Value::Float(*a as f64 + b)), @@ -471,7 +547,10 @@ impl VirtualMachine { (Value::Bounded(a), Value::Bounded(b)) => { let res = a.saturating_add(*b); if res > 0xFFFF { - Err(OpError::Trap(TRAP_OOB, format!("Bounded addition overflow: {} + {} = {}", a, b, res))) + Err(OpError::Trap( + TRAP_OOB, + format!("Bounded addition overflow: {} + {} = {}", a, b, res), + )) } else { Ok(Value::Bounded(res)) } @@ -490,7 +569,10 @@ impl VirtualMachine { (Value::Float(a), Value::Int64(b)) => Ok(Value::Float(a - b as f64)), (Value::Bounded(a), Value::Bounded(b)) => { if a < b { - Err(OpError::Trap(TRAP_OOB, format!("Bounded subtraction underflow: {} - {} < 0", a, b))) + Err(OpError::Trap( + TRAP_OOB, + format!("Bounded subtraction underflow: {} - {} < 0", a, b), + )) } else { Ok(Value::Bounded(a - b)) } @@ -510,7 +592,10 @@ impl VirtualMachine { (Value::Bounded(a), Value::Bounded(b)) => { let res = a as u64 * b as u64; if res > 0xFFFF { - Err(OpError::Trap(TRAP_OOB, format!("Bounded multiplication overflow: {} * {} = {}", a, b, res))) + Err(OpError::Trap( + TRAP_OOB, + format!("Bounded multiplication overflow: {} * {} = {}", a, b, res), + )) } else { Ok(Value::Bounded(res as u32)) } @@ -520,25 +605,37 @@ impl VirtualMachine { OpCode::Div => self.binary_op(opcode, start_pc as u32, |a, b| match (a, b) { (Value::Int32(a), Value::Int32(b)) => { if b == 0 { - return Err(OpError::Trap(TRAP_DIV_ZERO, "Integer division by zero".into())); + return Err(OpError::Trap( + TRAP_DIV_ZERO, + "Integer division by zero".into(), + )); } Ok(Value::Int32(a / b)) } (Value::Int64(a), Value::Int64(b)) => { if b == 0 { - return Err(OpError::Trap(TRAP_DIV_ZERO, "Integer division by zero".into())); + return Err(OpError::Trap( + TRAP_DIV_ZERO, + "Integer division by zero".into(), + )); } Ok(Value::Int64(a / b)) } (Value::Int32(a), Value::Int64(b)) => { if b == 0 { - return Err(OpError::Trap(TRAP_DIV_ZERO, "Integer division by zero".into())); + return Err(OpError::Trap( + TRAP_DIV_ZERO, + "Integer division by zero".into(), + )); } Ok(Value::Int64(a as i64 / b)) } (Value::Int64(a), Value::Int32(b)) => { if b == 0 { - return Err(OpError::Trap(TRAP_DIV_ZERO, "Integer division by zero".into())); + return Err(OpError::Trap( + TRAP_DIV_ZERO, + "Integer division by zero".into(), + )); } Ok(Value::Int64(a / b as i64)) } @@ -574,7 +671,10 @@ impl VirtualMachine { } (Value::Bounded(a), Value::Bounded(b)) => { if b == 0 { - return Err(OpError::Trap(TRAP_DIV_ZERO, "Bounded division by zero".into())); + return Err(OpError::Trap( + TRAP_DIV_ZERO, + "Bounded division by zero".into(), + )); } Ok(Value::Bounded(a / b)) } @@ -606,19 +706,34 @@ impl VirtualMachine { if let Value::Bounded(b) = val { self.push(Value::Int64(b as i64)); } else { - return Err(LogicalFrameEndingReason::Panic("Expected bounded for BOUND_TO_INT".into())); + return Err(LogicalFrameEndingReason::Panic( + "Expected bounded for BOUND_TO_INT".into(), + )); } } OpCode::IntToBoundChecked => { let val = self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?; - let int_val = val.as_integer().ok_or_else(|| LogicalFrameEndingReason::Panic("Expected integer for INT_TO_BOUND_CHECKED".into()))?; + let int_val = val.as_integer().ok_or_else(|| { + LogicalFrameEndingReason::Panic( + "Expected integer for INT_TO_BOUND_CHECKED".into(), + ) + })?; if int_val < 0 || int_val > 0xFFFF { - return Err(self.trap(TRAP_OOB, OpCode::IntToBoundChecked as u16, format!("Integer to bounded conversion out of range: {}", int_val), start_pc as u32)); + return Err(self.trap( + TRAP_OOB, + OpCode::IntToBoundChecked as u16, + format!("Integer to bounded conversion out of range: {}", int_val), + start_pc as u32, + )); } self.push(Value::Bounded(int_val as u32)); } - OpCode::Eq => self.binary_op(opcode, start_pc as u32, |a, b| Ok(Value::Boolean(a == b)))?, - OpCode::Neq => self.binary_op(opcode, start_pc as u32, |a, b| Ok(Value::Boolean(a != b)))?, + OpCode::Eq => { + self.binary_op(opcode, start_pc as u32, |a, b| Ok(Value::Boolean(a == b)))? + } + OpCode::Neq => { + self.binary_op(opcode, start_pc as u32, |a, b| Ok(Value::Boolean(a != b)))? + } OpCode::Lt => self.binary_op(opcode, start_pc as u32, |a, b| { a.partial_cmp(&b) .map(|o| Value::Boolean(o == std::cmp::Ordering::Less)) @@ -679,14 +794,18 @@ impl VirtualMachine { OpCode::Shl => self.binary_op(opcode, start_pc as u32, |a, b| match (a, b) { (Value::Int32(a), Value::Int32(b)) => Ok(Value::Int32(a.wrapping_shl(b as u32))), (Value::Int64(a), Value::Int64(b)) => Ok(Value::Int64(a.wrapping_shl(b as u32))), - (Value::Int32(a), Value::Int64(b)) => Ok(Value::Int64((a as i64).wrapping_shl(b as u32))), + (Value::Int32(a), Value::Int64(b)) => { + Ok(Value::Int64((a as i64).wrapping_shl(b as u32))) + } (Value::Int64(a), Value::Int32(b)) => Ok(Value::Int64(a.wrapping_shl(b as u32))), _ => Err(OpError::Panic("Invalid types for Shl".into())), })?, OpCode::Shr => self.binary_op(opcode, start_pc as u32, |a, b| match (a, b) { (Value::Int32(a), Value::Int32(b)) => Ok(Value::Int32(a.wrapping_shr(b as u32))), (Value::Int64(a), Value::Int64(b)) => Ok(Value::Int64(a.wrapping_shr(b as u32))), - (Value::Int32(a), Value::Int64(b)) => Ok(Value::Int64((a as i64).wrapping_shr(b as u32))), + (Value::Int32(a), Value::Int64(b)) => { + Ok(Value::Int64((a as i64).wrapping_shr(b as u32))) + } (Value::Int64(a), Value::Int32(b)) => Ok(Value::Int64(a.wrapping_shr(b as u32))), _ => Err(OpError::Panic("Invalid types for Shr".into())), })?, @@ -696,16 +815,26 @@ impl VirtualMachine { Value::Int32(a) => self.push(Value::Int32(a.wrapping_neg())), Value::Int64(a) => self.push(Value::Int64(a.wrapping_neg())), Value::Float(a) => self.push(Value::Float(-a)), - _ => return Err(LogicalFrameEndingReason::Panic("Invalid type for Neg".into())), + _ => { + return Err(LogicalFrameEndingReason::Panic("Invalid type for Neg".into())); + } } } OpCode::GetGlobal => { - let idx = instr.imm_u32().map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))? as usize; - let val = self.globals.get(idx).cloned().ok_or_else(|| LogicalFrameEndingReason::Panic("Invalid global index".into()))?; + let idx = instr + .imm_u32() + .map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))? + as usize; + let val = self.globals.get(idx).cloned().ok_or_else(|| { + LogicalFrameEndingReason::Panic("Invalid global index".into()) + })?; self.push(val); } OpCode::SetGlobal => { - let idx = instr.imm_u32().map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))? as usize; + let idx = instr + .imm_u32() + .map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))? + as usize; let val = self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?; if idx >= self.globals.len() { self.globals.resize(idx + 1, Value::Null); @@ -713,44 +842,80 @@ impl VirtualMachine { self.globals[idx] = val; } OpCode::GetLocal => { - let slot = instr.imm_u32().map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))?; - let frame = self.call_stack.last().ok_or_else(|| LogicalFrameEndingReason::Panic("No active call frame".into()))?; + let slot = instr + .imm_u32() + .map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))?; + let frame = self.call_stack.last().ok_or_else(|| { + LogicalFrameEndingReason::Panic("No active call frame".into()) + })?; let func = &self.program.functions[frame.func_idx]; - - crate::local_addressing::check_local_slot(func, slot, opcode as u16, start_pc as u32) - .map_err(|trap_info| self.trap(trap_info.code, trap_info.opcode, trap_info.message, trap_info.pc))?; - + + crate::local_addressing::check_local_slot( + func, + slot, + opcode as u16, + start_pc as u32, + ) + .map_err(|trap_info| { + self.trap(trap_info.code, trap_info.opcode, trap_info.message, trap_info.pc) + })?; + let stack_idx = crate::local_addressing::local_index(frame, slot); - let val = self.operand_stack.get(stack_idx).cloned().ok_or_else(|| LogicalFrameEndingReason::Panic("Internal error: validated local slot not found in stack".into()))?; + let val = self.operand_stack.get(stack_idx).cloned().ok_or_else(|| { + LogicalFrameEndingReason::Panic( + "Internal error: validated local slot not found in stack".into(), + ) + })?; self.push(val); } OpCode::SetLocal => { - let slot = instr.imm_u32().map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))?; + let slot = instr + .imm_u32() + .map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))?; let val = self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?; - let frame = self.call_stack.last().ok_or_else(|| LogicalFrameEndingReason::Panic("No active call frame".into()))?; + let frame = self.call_stack.last().ok_or_else(|| { + LogicalFrameEndingReason::Panic("No active call frame".into()) + })?; let func = &self.program.functions[frame.func_idx]; - crate::local_addressing::check_local_slot(func, slot, opcode as u16, start_pc as u32) - .map_err(|trap_info| self.trap(trap_info.code, trap_info.opcode, trap_info.message, trap_info.pc))?; - + crate::local_addressing::check_local_slot( + func, + slot, + opcode as u16, + start_pc as u32, + ) + .map_err(|trap_info| { + self.trap(trap_info.code, trap_info.opcode, trap_info.message, trap_info.pc) + })?; + let stack_idx = crate::local_addressing::local_index(frame, slot); self.operand_stack[stack_idx] = val; } OpCode::Call => { - let func_id = instr.imm_u32().map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))? as usize; + let func_id = instr + .imm_u32() + .map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))? + as usize; let callee = self.program.functions.get(func_id).ok_or_else(|| { - self.trap(TRAP_INVALID_FUNC, opcode as u16, format!("Invalid func_id {}", func_id), start_pc as u32) + self.trap( + TRAP_INVALID_FUNC, + opcode as u16, + format!("Invalid func_id {}", func_id), + start_pc as u32, + ) })?; - + if self.operand_stack.len() < callee.param_slots as usize { return Err(LogicalFrameEndingReason::Panic(format!( "Stack underflow during CALL to func {}: expected at least {} arguments, got {}", - func_id, callee.param_slots, self.operand_stack.len() + func_id, + callee.param_slots, + self.operand_stack.len() ))); } let stack_base = self.operand_stack.len() - callee.param_slots as usize; - + // Allocate and zero-init local_slots for _ in 0..callee.local_slots { self.operand_stack.push(Value::Null); @@ -764,13 +929,18 @@ impl VirtualMachine { self.pc = callee.code_offset as usize; } OpCode::Ret => { - let frame = self.call_stack.pop().ok_or_else(|| LogicalFrameEndingReason::Panic("Call stack underflow".into()))?; + let frame = self.call_stack.pop().ok_or_else(|| { + LogicalFrameEndingReason::Panic("Call stack underflow".into()) + })?; let func = &self.program.functions[frame.func_idx]; let return_slots = func.return_slots as usize; - + let current_height = self.operand_stack.len(); - let expected_height = frame.stack_base + func.param_slots as usize + func.local_slots as usize + return_slots; - + let expected_height = frame.stack_base + + func.param_slots as usize + + func.local_slots as usize + + return_slots; + if current_height != expected_height { return Err(self.trap(TRAP_BAD_RET_SLOTS, opcode as u16, format!( "Incorrect stack height at RET in func {}: expected {} slots (stack_base={} + params={} + locals={} + returns={}), got {}", @@ -784,7 +954,7 @@ impl VirtualMachine { return_vals.push(self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?); } return_vals.reverse(); - + self.operand_stack.truncate(frame.stack_base); for val in return_vals { self.push(val); @@ -792,17 +962,19 @@ impl VirtualMachine { self.pc = frame.return_pc as usize; } OpCode::PushScope => { - self.scope_stack.push(ScopeFrame { - scope_stack_base: self.operand_stack.len(), - }); + self.scope_stack.push(ScopeFrame { scope_stack_base: self.operand_stack.len() }); } OpCode::PopScope => { - let frame = self.scope_stack.pop().ok_or_else(|| LogicalFrameEndingReason::Panic("Scope stack underflow".into()))?; + let frame = self.scope_stack.pop().ok_or_else(|| { + LogicalFrameEndingReason::Panic("Scope stack underflow".into()) + })?; self.operand_stack.truncate(frame.scope_stack_base); } OpCode::Alloc => { // Allocate a new gate with given type and number of slots. - let (type_id, slots_u32) = instr.imm_u32x2().map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))?; + let (type_id, slots_u32) = instr + .imm_u32x2() + .map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))?; let slots = slots_u32 as usize; // Bump-allocate on the heap and zero-initialize with Null. @@ -812,12 +984,8 @@ impl VirtualMachine { } // Insert entry into gate pool; GateId is index in this pool. - let entry = GateEntry { - alive: true, - base: base_idx as u32, - slots: slots_u32, - type_id, - }; + let entry = + GateEntry { alive: true, base: base_idx as u32, slots: slots_u32, type_id }; let gate_id = self.gate_pool.len() as u32; self.gate_pool.push(entry); @@ -825,7 +993,10 @@ impl VirtualMachine { self.push(Value::Gate(gate_id as usize)); } OpCode::GateLoad => { - let offset = instr.imm_u32().map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))? as usize; + let offset = instr + .imm_u32() + .map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))? + as usize; let ref_val = self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?; if let Value::Gate(gid_usize) = ref_val { let gid = gid_usize as GateId; @@ -835,20 +1006,38 @@ impl VirtualMachine { // bounds check against slots let off_u32 = offset as u32; if off_u32 >= entry.slots { - return Err(self.trap(TRAP_OOB, OpCode::GateLoad as u16, format!("Out-of-bounds heap access at offset {}", offset), start_pc as u32)); + return Err(self.trap( + TRAP_OOB, + OpCode::GateLoad as u16, + format!("Out-of-bounds heap access at offset {}", offset), + start_pc as u32, + )); } let heap_idx = entry.base as usize + offset; let val = self.heap.get(heap_idx).cloned().ok_or_else(|| { // Should not happen if pool and heap are consistent, but keep defensive. - self.trap(TRAP_OOB, OpCode::GateLoad as u16, format!("Out-of-bounds heap access at offset {}", offset), start_pc as u32) + self.trap( + TRAP_OOB, + OpCode::GateLoad as u16, + format!("Out-of-bounds heap access at offset {}", offset), + start_pc as u32, + ) })?; self.push(val); } else { - return Err(self.trap(TRAP_TYPE, OpCode::GateLoad as u16, "Expected gate handle for GATE_LOAD".to_string(), start_pc as u32)); + return Err(self.trap( + TRAP_TYPE, + OpCode::GateLoad as u16, + "Expected gate handle for GATE_LOAD".to_string(), + start_pc as u32, + )); } } OpCode::GateStore => { - let offset = instr.imm_u32().map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))? as usize; + let offset = instr + .imm_u32() + .map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))? + as usize; let val = self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?; let ref_val = self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?; if let Value::Gate(gid_usize) = ref_val { @@ -858,30 +1047,54 @@ impl VirtualMachine { .map_err(|t| t)?; let off_u32 = offset as u32; if off_u32 >= entry.slots { - return Err(self.trap(TRAP_OOB, OpCode::GateStore as u16, format!("Out-of-bounds heap access at offset {}", offset), start_pc as u32)); + return Err(self.trap( + TRAP_OOB, + OpCode::GateStore as u16, + format!("Out-of-bounds heap access at offset {}", offset), + start_pc as u32, + )); } let heap_idx = entry.base as usize + offset; if heap_idx >= self.heap.len() { - return Err(self.trap(TRAP_OOB, OpCode::GateStore as u16, format!("Out-of-bounds heap access at offset {}", offset), start_pc as u32)); + return Err(self.trap( + TRAP_OOB, + OpCode::GateStore as u16, + format!("Out-of-bounds heap access at offset {}", offset), + start_pc as u32, + )); } self.heap[heap_idx] = val; } else { - return Err(self.trap(TRAP_TYPE, OpCode::GateStore as u16, "Expected gate handle for GATE_STORE".to_string(), start_pc as u32)); + return Err(self.trap( + TRAP_TYPE, + OpCode::GateStore as u16, + "Expected gate handle for GATE_STORE".to_string(), + start_pc as u32, + )); } } - OpCode::GateBeginPeek | OpCode::GateEndPeek | - OpCode::GateBeginBorrow | OpCode::GateEndBorrow | - OpCode::GateBeginMutate | OpCode::GateEndMutate | - OpCode::GateRetain => { - } + OpCode::GateBeginPeek + | OpCode::GateEndPeek + | OpCode::GateBeginBorrow + | OpCode::GateEndBorrow + | OpCode::GateBeginMutate + | OpCode::GateEndMutate + | OpCode::GateRetain => {} OpCode::GateRelease => { self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?; } OpCode::Syscall => { let pc_at_syscall = start_pc as u32; - let id = instr.imm_u32().map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))?; + let id = instr + .imm_u32() + .map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))?; let syscall = prometeu_hal::syscalls::Syscall::from_u32(id).ok_or_else(|| { - self.trap(TRAP_INVALID_SYSCALL, OpCode::Syscall as u16, format!("Unknown syscall: 0x{:08X}", id), pc_at_syscall) + self.trap( + TRAP_INVALID_SYSCALL, + OpCode::Syscall as u16, + format!("Unknown syscall: 0x{:08X}", id), + pc_at_syscall, + ) })?; let args_count = syscall.args_count(); @@ -889,7 +1102,12 @@ impl VirtualMachine { let mut args = Vec::with_capacity(args_count); for _ in 0..args_count { let v = self.pop().map_err(|_e| { - self.trap(TRAP_STACK_UNDERFLOW, OpCode::Syscall as u16, "Syscall argument stack underflow".to_string(), pc_at_syscall) + self.trap( + TRAP_STACK_UNDERFLOW, + OpCode::Syscall as u16, + "Syscall argument stack underflow".to_string(), + pc_at_syscall, + ) })?; args.push(v); } @@ -898,9 +1116,13 @@ impl VirtualMachine { let stack_height_before = self.operand_stack.len(); let mut ret = crate::HostReturn::new(&mut self.operand_stack); native.syscall(id, &args, &mut ret, ctx).map_err(|fault| match fault { - VmFault::Trap(code, msg) => self.trap(code, OpCode::Syscall as u16, msg, pc_at_syscall), + VmFault::Trap(code, msg) => { + self.trap(code, OpCode::Syscall as u16, msg, pc_at_syscall) + } VmFault::Panic(msg) => LogicalFrameEndingReason::Panic(msg), - VmFault::Unavailable => LogicalFrameEndingReason::Panic("Host feature unavailable".into()), + VmFault::Unavailable => { + LogicalFrameEndingReason::Panic("Host feature unavailable".into()) + } })?; let stack_height_after = self.operand_stack.len(); @@ -908,7 +1130,10 @@ impl VirtualMachine { if results_pushed != syscall.results_count() { return Err(LogicalFrameEndingReason::Panic(format!( "Syscall {} (0x{:08X}) results mismatch: expected {}, got {}", - syscall.name(), id, syscall.results_count(), results_pushed + syscall.name(), + id, + syscall.results_count(), + results_pushed ))); } } @@ -923,15 +1148,15 @@ impl VirtualMachine { } /// Resolves a `GateId` to an immutable reference to its `GateEntry` or returns a trap reason. - fn resolve_gate(&self, gate_id: GateId, opcode: u16, pc: u32) -> Result<&GateEntry, LogicalFrameEndingReason> { + fn resolve_gate( + &self, + gate_id: GateId, + opcode: u16, + pc: u32, + ) -> Result<&GateEntry, LogicalFrameEndingReason> { let idx = gate_id as usize; let entry = self.gate_pool.get(idx).ok_or_else(|| { - self.trap( - TRAP_INVALID_GATE, - opcode, - format!("Invalid gate id: {}", gate_id), - pc, - ) + self.trap(TRAP_INVALID_GATE, opcode, format!("Invalid gate id: {}", gate_id), pc) })?; if !entry.alive { return Err(self.trap( @@ -944,7 +1169,13 @@ impl VirtualMachine { Ok(entry) } - pub fn trap(&self, code: u32, opcode: u16, message: String, pc: u32) -> LogicalFrameEndingReason { + pub fn trap( + &self, + code: u32, + opcode: u16, + message: String, + pc: u32, + ) -> LogicalFrameEndingReason { LogicalFrameEndingReason::Trap(self.program.create_trap(code, opcode, message, pc)) } @@ -973,7 +1204,12 @@ impl VirtualMachine { self.operand_stack.last().ok_or("Stack underflow".into()) } - fn binary_op(&mut self, opcode: OpCode, start_pc: u32, f: F) -> Result<(), LogicalFrameEndingReason> + fn binary_op( + &mut self, + opcode: OpCode, + start_pc: u32, + f: F, + ) -> Result<(), LogicalFrameEndingReason> where F: FnOnce(Value, Value) -> Result, { @@ -1010,7 +1246,13 @@ mod tests { struct MockNative; impl NativeInterface for MockNative { - fn syscall(&mut self, _id: u32, _args: &[Value], _ret: &mut HostReturn, _ctx: &mut HostContext) -> Result<(), VmFault> { + fn syscall( + &mut self, + _id: u32, + _args: &[Value], + _ret: &mut HostReturn, + _ctx: &mut HostContext, + ) -> Result<(), VmFault> { Ok(()) } } @@ -1041,7 +1283,7 @@ mod tests { let mut vm = new_test_vm(rom.clone(), vec![]); vm.run_budget(100, &mut native, &mut ctx).unwrap(); - + assert_eq!(vm.pop().unwrap(), Value::Int32(0)); } @@ -1060,7 +1302,7 @@ mod tests { let mut vm = new_test_vm(rom.clone(), vec![]); let report = vm.run_budget(100, &mut native, &mut ctx).unwrap(); - + match report.reason { LogicalFrameEndingReason::Trap(trap) => { assert_eq!(trap.code, TRAP_DIV_ZERO); @@ -1083,7 +1325,7 @@ mod tests { let mut vm = new_test_vm(rom.clone(), vec![]); let report = vm.run_budget(100, &mut native, &mut ctx).unwrap(); - + match report.reason { LogicalFrameEndingReason::Trap(trap) => { assert_eq!(trap.code, TRAP_OOB); @@ -1108,7 +1350,7 @@ mod tests { let mut vm = new_test_vm(rom.clone(), vec![]); let report = vm.run_budget(100, &mut native, &mut ctx).unwrap(); - + match report.reason { LogicalFrameEndingReason::Trap(trap) => { assert_eq!(trap.code, TRAP_OOB); @@ -1207,7 +1449,7 @@ mod tests { #[test] fn test_call_ret_scope_separation() { let mut rom = Vec::new(); - + // entrypoint: // PUSH_I64 10 // CALL func_id 1 @@ -1217,7 +1459,7 @@ mod tests { rom.extend_from_slice(&(OpCode::Call as u16).to_le_bytes()); rom.extend_from_slice(&1u32.to_le_bytes()); // func_id 1 rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); - + let func_addr = rom.len(); // func: @@ -1244,17 +1486,23 @@ mod tests { let functions = vec![ FunctionMeta { code_offset: 0, code_len: func_addr as u32, ..Default::default() }, - FunctionMeta { - code_offset: func_addr as u32, - code_len: (rom.len() - func_addr) as u32, - param_slots: 1, - return_slots: 1, - ..Default::default() + FunctionMeta { + code_offset: func_addr as u32, + code_len: (rom.len() - func_addr) as u32, + param_slots: 1, + return_slots: 1, + ..Default::default() }, ]; let mut vm = VirtualMachine { - program: ProgramImage::new(rom, vec![], functions, None, std::collections::HashMap::new()), + program: ProgramImage::new( + rom, + vec![], + functions, + None, + std::collections::HashMap::new(), + ), ..Default::default() }; vm.prepare_call("0"); @@ -1282,24 +1530,30 @@ mod tests { rom.extend_from_slice(&(OpCode::Call as u16).to_le_bytes()); rom.extend_from_slice(&1u32.to_le_bytes()); // func_id 1 rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); - + let func_addr = rom.len(); // func: RET (SEM VALOR ANTES) rom.extend_from_slice(&(OpCode::Ret as u16).to_le_bytes()); let functions = vec![ FunctionMeta { code_offset: 0, code_len: func_addr as u32, ..Default::default() }, - FunctionMeta { - code_offset: func_addr as u32, - code_len: (rom.len() - func_addr) as u32, - param_slots: 0, - return_slots: 1, - ..Default::default() + FunctionMeta { + code_offset: func_addr as u32, + code_len: (rom.len() - func_addr) as u32, + param_slots: 0, + return_slots: 1, + ..Default::default() }, ]; let mut vm = VirtualMachine { - program: ProgramImage::new(rom, vec![], functions, None, std::collections::HashMap::new()), + program: ProgramImage::new( + rom, + vec![], + functions, + None, + std::collections::HashMap::new(), + ), ..Default::default() }; vm.prepare_call("0"); @@ -1328,24 +1582,30 @@ mod tests { let functions2 = vec![ FunctionMeta { code_offset: 0, code_len: func_addr2 as u32, ..Default::default() }, - FunctionMeta { - code_offset: func_addr2 as u32, - code_len: (rom2.len() - func_addr2) as u32, - param_slots: 0, - return_slots: 1, - ..Default::default() + FunctionMeta { + code_offset: func_addr2 as u32, + code_len: (rom2.len() - func_addr2) as u32, + param_slots: 0, + return_slots: 1, + ..Default::default() }, ]; let mut vm2 = VirtualMachine { - program: ProgramImage::new(rom2, vec![], functions2, None, std::collections::HashMap::new()), + program: ProgramImage::new( + rom2, + vec![], + functions2, + None, + std::collections::HashMap::new(), + ), ..Default::default() }; vm2.prepare_call("0"); vm2.step(&mut native, &mut ctx).unwrap(); // CALL vm2.step(&mut native, &mut ctx).unwrap(); // PUSH_I64 vm2.step(&mut native, &mut ctx).unwrap(); // RET - + assert_eq!(vm2.operand_stack.len(), 1); assert_eq!(vm2.pop().unwrap(), Value::Int64(123)); } @@ -1353,7 +1613,7 @@ mod tests { #[test] fn test_nested_scopes() { let mut rom = Vec::new(); - + // PUSH_I64 1 // PUSH_SCOPE // PUSH_I64 2 @@ -1381,26 +1641,26 @@ mod tests { // Execute step by step and check stack vm.step(&mut native, &mut ctx).unwrap(); // Push 1 assert_eq!(vm.operand_stack.len(), 1); - + vm.step(&mut native, &mut ctx).unwrap(); // PushScope 1 assert_eq!(vm.scope_stack.len(), 1); assert_eq!(vm.scope_stack.last().unwrap().scope_stack_base, 1); - + vm.step(&mut native, &mut ctx).unwrap(); // Push 2 assert_eq!(vm.operand_stack.len(), 2); - + vm.step(&mut native, &mut ctx).unwrap(); // PushScope 2 assert_eq!(vm.scope_stack.len(), 2); assert_eq!(vm.scope_stack.last().unwrap().scope_stack_base, 2); - + vm.step(&mut native, &mut ctx).unwrap(); // Push 3 assert_eq!(vm.operand_stack.len(), 3); - + vm.step(&mut native, &mut ctx).unwrap(); // PopScope 2 assert_eq!(vm.scope_stack.len(), 1); assert_eq!(vm.operand_stack.len(), 2); assert_eq!(vm.operand_stack.last().unwrap(), &Value::Int64(2)); - + vm.step(&mut native, &mut ctx).unwrap(); // PopScope 1 assert_eq!(vm.scope_stack.len(), 0); assert_eq!(vm.operand_stack.len(), 1); @@ -1410,7 +1670,7 @@ mod tests { #[test] fn test_pop_scope_does_not_affect_ret() { let mut rom = Vec::new(); - + // PUSH_I64 100 // CALL func_id 1 // HALT @@ -1419,14 +1679,14 @@ mod tests { rom.extend_from_slice(&(OpCode::Call as u16).to_le_bytes()); rom.extend_from_slice(&1u32.to_le_bytes()); // func_id 1 rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); - + let func_addr = rom.len(); // func: // PUSH_I64 200 // PUSH_SCOPE // PUSH_I64 300 // RET - + rom.extend_from_slice(&(OpCode::PushI64 as u16).to_le_bytes()); rom.extend_from_slice(&200i64.to_le_bytes()); rom.extend_from_slice(&(OpCode::PushScope as u16).to_le_bytes()); @@ -1437,17 +1697,23 @@ mod tests { let functions = vec![ FunctionMeta { code_offset: 0, code_len: func_addr as u32, ..Default::default() }, - FunctionMeta { - code_offset: func_addr as u32, - code_len: (rom.len() - func_addr) as u32, - param_slots: 0, - return_slots: 1, - ..Default::default() + FunctionMeta { + code_offset: func_addr as u32, + code_len: (rom.len() - func_addr) as u32, + param_slots: 0, + return_slots: 1, + ..Default::default() }, ]; let mut vm = VirtualMachine { - program: ProgramImage::new(rom, vec![], functions, None, std::collections::HashMap::new()), + program: ProgramImage::new( + rom, + vec![], + functions, + None, + std::collections::HashMap::new(), + ), ..Default::default() }; vm.prepare_call("0"); @@ -1584,12 +1850,12 @@ mod tests { // 11-12: PushI32 (Offset 11) // 13-16: 100 (i32) // 17-18: Halt - + let mut rom = Vec::new(); rom.extend_from_slice(&(OpCode::PushBool as u16).to_le_bytes()); rom.push(1); rom.extend_from_slice(&(OpCode::JmpIfTrue as u16).to_le_bytes()); - rom.extend_from_slice(&(11u32).to_le_bytes()); + rom.extend_from_slice(&(11u32).to_le_bytes()); rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); // Offset 9 rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes()); // Offset 11 rom.extend_from_slice(&100i32.to_le_bytes()); @@ -1616,7 +1882,7 @@ mod tests { let mut vm = new_test_vm(rom.clone(), vec![]); let report = vm.run_budget(100, &mut native, &mut ctx).unwrap(); - + assert_eq!(report.reason, LogicalFrameEndingReason::Breakpoint); assert_eq!(vm.pc, 8); // PushI32 (6 bytes) + Trap (2 bytes) assert_eq!(vm.peek().unwrap(), &Value::Int32(42)); @@ -1640,7 +1906,7 @@ mod tests { let mut vm = new_test_vm(rom.clone(), vec![]); vm.run_budget(100, &mut native, &mut ctx).unwrap(); - + assert_eq!(vm.pop().unwrap(), Value::Int32(1)); assert!(vm.pop().is_err()); // Stack should be empty } @@ -1662,7 +1928,7 @@ mod tests { let mut vm = new_test_vm(rom.clone(), vec![]); let report = vm.run_budget(100, &mut native, &mut ctx).unwrap(); - + match report.reason { LogicalFrameEndingReason::Trap(trap) => { assert_eq!(trap.code, TRAP_OOB); @@ -1689,7 +1955,7 @@ mod tests { let mut vm = new_test_vm(rom.clone(), vec![]); let report = vm.run_budget(100, &mut native, &mut ctx).unwrap(); - + match report.reason { LogicalFrameEndingReason::Trap(trap) => { assert_eq!(trap.code, TRAP_TYPE); @@ -1782,7 +2048,7 @@ mod tests { 0x17, 0x00, // PushI32 0x00, 0x00, 0x00, 0x00, // value 0 0x11, 0x00, // Pop - 0x51, 0x00 // Ret + 0x51, 0x00, // Ret ]; let mut vm = VirtualMachine::new(rom.clone(), vec![]); vm.program.functions = std::sync::Arc::from(vec![prometeu_bytecode::FunctionMeta { @@ -1793,10 +2059,18 @@ mod tests { let mut ctx = HostContext::new(None); struct TestNative; impl NativeInterface for TestNative { - fn syscall(&mut self, _id: u32, _args: &[Value], _ret: &mut HostReturn, _ctx: &mut HostContext) -> Result<(), VmFault> { Ok(()) } + fn syscall( + &mut self, + _id: u32, + _args: &[Value], + _ret: &mut HostReturn, + _ctx: &mut HostContext, + ) -> Result<(), VmFault> { + Ok(()) + } } let mut native = TestNative; - + vm.prepare_call("0"); let result = vm.run_budget(100, &mut native, &mut ctx).expect("VM run failed"); assert_eq!(result.reason, LogicalFrameEndingReason::EndOfRom); @@ -1808,17 +2082,23 @@ mod tests { 0x70, 0x00, // Syscall + Reserved 0x01, 0x00, 0x00, 0x00, // Syscall ID 1 ]; - + struct MultiReturnNative; impl NativeInterface for MultiReturnNative { - fn syscall(&mut self, _id: u32, _args: &[Value], ret: &mut HostReturn, _ctx: &mut HostContext) -> Result<(), VmFault> { + fn syscall( + &mut self, + _id: u32, + _args: &[Value], + ret: &mut HostReturn, + _ctx: &mut HostContext, + ) -> Result<(), VmFault> { ret.push_bool(true); ret.push_int(42); ret.push_bounded(255)?; Ok(()) } } - + let mut vm = VirtualMachine::new(rom.clone(), vec![]); vm.program.functions = std::sync::Arc::from(vec![prometeu_bytecode::FunctionMeta { code_offset: 0, @@ -1827,10 +2107,10 @@ mod tests { }]); let mut native = MultiReturnNative; let mut ctx = HostContext::new(None); - + vm.prepare_call("0"); vm.run_budget(100, &mut native, &mut ctx).unwrap(); - + assert_eq!(vm.pop().unwrap(), Value::Bounded(255)); assert_eq!(vm.pop().unwrap(), Value::Int64(42)); assert_eq!(vm.pop().unwrap(), Value::Boolean(true)); @@ -1842,14 +2122,20 @@ mod tests { 0x70, 0x00, // Syscall + Reserved 0x01, 0x00, 0x00, 0x00, // Syscall ID 1 ]; - + struct VoidReturnNative; impl NativeInterface for VoidReturnNative { - fn syscall(&mut self, _id: u32, _args: &[Value], _ret: &mut HostReturn, _ctx: &mut HostContext) -> Result<(), VmFault> { + fn syscall( + &mut self, + _id: u32, + _args: &[Value], + _ret: &mut HostReturn, + _ctx: &mut HostContext, + ) -> Result<(), VmFault> { Ok(()) } } - + let mut vm = VirtualMachine::new(rom.clone(), vec![]); vm.program.functions = std::sync::Arc::from(vec![prometeu_bytecode::FunctionMeta { code_offset: 0, @@ -1858,11 +2144,11 @@ mod tests { }]); let mut native = VoidReturnNative; let mut ctx = HostContext::new(None); - + vm.prepare_call("0"); vm.operand_stack.push(Value::Int32(100)); vm.run_budget(100, &mut native, &mut ctx).unwrap(); - + assert_eq!(vm.pop().unwrap(), Value::Int32(100)); assert!(vm.operand_stack.is_empty()); } @@ -1876,15 +2162,21 @@ mod tests { 0x70, 0x00, // Syscall + Reserved 0x01, 0x10, 0x00, 0x00, // Syscall ID 0x1001 ]; - + struct ArgCheckNative; impl NativeInterface for ArgCheckNative { - fn syscall(&mut self, _id: u32, args: &[Value], _ret: &mut HostReturn, _ctx: &mut HostContext) -> Result<(), VmFault> { + fn syscall( + &mut self, + _id: u32, + args: &[Value], + _ret: &mut HostReturn, + _ctx: &mut HostContext, + ) -> Result<(), VmFault> { expect_int(args, 0)?; Ok(()) } } - + let mut vm = VirtualMachine::new(rom.clone(), vec![]); vm.program.functions = std::sync::Arc::from(vec![prometeu_bytecode::FunctionMeta { code_offset: 0, @@ -1893,10 +2185,10 @@ mod tests { }]); let mut native = ArgCheckNative; 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_TYPE); @@ -1964,20 +2256,26 @@ mod tests { 0x70, 0x00, // Syscall + Reserved 0x10, 0x10, 0x00, 0x00, // Syscall ID 0x1010 ]; - + struct BadNative; impl NativeInterface for BadNative { - fn syscall(&mut self, _id: u32, _args: &[Value], ret: &mut HostReturn, _ctx: &mut HostContext) -> Result<(), VmFault> { + fn syscall( + &mut self, + _id: u32, + _args: &[Value], + ret: &mut HostReturn, + _ctx: &mut HostContext, + ) -> Result<(), VmFault> { // Wrong: GfxClear565 is void but we push something ret.push_int(42); Ok(()) } } - + let mut vm = new_test_vm(rom.clone(), vec![]); let mut native = BadNative; let mut ctx = HostContext::new(None); - + vm.prepare_call("0"); let report = vm.run_budget(100, &mut native, &mut ctx).unwrap(); match report.reason { @@ -2015,7 +2313,7 @@ mod tests { let mut header = vec![0u8; 32]; header[0..4].copy_from_slice(b"PBS\0"); header[4..6].copy_from_slice(&1u16.to_le_bytes()); // version 1 (unsupported) - + let res = vm.initialize(header, ""); assert_eq!(res, Err(VmInitError::UnsupportedFormat)); } @@ -2026,10 +2324,10 @@ mod tests { let mut header = vec![0u8; 32]; header[0..4].copy_from_slice(b"PBS\0"); header[8..12].copy_from_slice(&1u32.to_le_bytes()); // 1 section claimed but none provided - + let res = vm.initialize(header, ""); match res { - Err(VmInitError::ImageLoadFailed(prometeu_bytecode::LoadError::UnexpectedEof)) => {}, + Err(VmInitError::ImageLoadFailed(prometeu_bytecode::LoadError::UnexpectedEof)) => {} _ => panic!("Expected PbsV0LoadFailed(UnexpectedEof), got {:?}", res), } } @@ -2040,11 +2338,11 @@ mod tests { // Valid empty PBS v0 module let mut header = vec![0u8; 32]; header[0..4].copy_from_slice(b"PBS\0"); - + // Try to initialize with numeric entrypoint 10 (out of bounds for empty ROM) let res = vm.initialize(header, "10"); assert_eq!(res, Err(VmInitError::EntrypointNotFound)); - + // VM state should not be updated assert_eq!(vm.pc, 0); assert_eq!(vm.program.rom.len(), 0); @@ -2054,10 +2352,10 @@ mod tests { fn test_loader_hardening_successful_init() { let mut vm = VirtualMachine::default(); vm.pc = 123; // Pollution - + let mut header = vec![0u8; 32]; header[0..4].copy_from_slice(b"PBS\0"); - + let res = vm.initialize(header, ""); assert!(res.is_ok()); assert_eq!(vm.pc, 0); @@ -2065,7 +2363,6 @@ mod tests { assert_eq!(vm.cycles, 0); } - #[test] fn test_calling_convention_add() { let mut native = MockNative; @@ -2241,7 +2538,7 @@ mod tests { let mut vm = new_test_vm(rom.clone(), vec![]); let report = vm.run_budget(100, &mut native, &mut ctx).unwrap(); - + match report.reason { LogicalFrameEndingReason::Trap(trap) => { assert_eq!(trap.code, TRAP_INVALID_FUNC); @@ -2290,7 +2587,7 @@ mod tests { 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_BAD_RET_SLOTS); @@ -2395,8 +2692,8 @@ mod tests { vm.prepare_call("0"); let report = vm.run_budget(100, &mut native, &mut ctx).unwrap(); assert_eq!(report.reason, LogicalFrameEndingReason::Halted); - - // The last value on stack is the return of the second CALL 1, + + // The last value on stack is the return of the second CALL 1, // which should be Value::Null because locals are zero-initialized on each call. assert_eq!(vm.operand_stack.last().unwrap(), &Value::Null); } @@ -2423,7 +2720,7 @@ mod tests { 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_LOCAL); @@ -2456,7 +2753,7 @@ mod tests { // 3: JMP_IF_FALSE -> ELSE1 (offset 42) rom.extend_from_slice(&(OpCode::JmpIfFalse as u16).to_le_bytes()); rom.extend_from_slice(&42u32.to_le_bytes()); - + // INNER IF: // 9: PUSH_BOOL false rom.extend_from_slice(&(OpCode::PushBool as u16).to_le_bytes()); @@ -2470,7 +2767,7 @@ mod tests { // 24: JMP -> END (offset 48) rom.extend_from_slice(&(OpCode::Jmp as u16).to_le_bytes()); rom.extend_from_slice(&48u32.to_le_bytes()); - + // ELSE2: // 30: PUSH_I32 2 rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes()); @@ -2478,12 +2775,12 @@ mod tests { // 36: JMP -> END (offset 48) rom.extend_from_slice(&(OpCode::Jmp as u16).to_le_bytes()); rom.extend_from_slice(&48u32.to_le_bytes()); - + // ELSE1: // 42: PUSH_I32 3 rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes()); rom.extend_from_slice(&3i32.to_le_bytes()); - + // END: // 48: HALT rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); @@ -2496,7 +2793,7 @@ mod tests { ..Default::default() }]); vm.prepare_call("0"); - + vm.run_budget(100, &mut native, &mut ctx).unwrap(); assert_eq!(vm.pop().unwrap(), Value::Int32(2)); } @@ -2534,7 +2831,7 @@ mod tests { ..Default::default() }]); vm.prepare_call("0"); - + let report = vm.run_budget(100, &mut native, &mut ctx).unwrap(); assert_eq!(report.reason, LogicalFrameEndingReason::Halted); assert_eq!(vm.operand_stack.len(), 0); @@ -2562,7 +2859,7 @@ mod tests { ..Default::default() }]); vm.prepare_call("0"); - + let report = vm.run_budget(100, &mut native, &mut ctx).unwrap(); match report.reason { LogicalFrameEndingReason::Trap(trap) => { diff --git a/crates/console/prometeu-vm/tests/smoke.rs b/crates/console/prometeu-vm/tests/smoke.rs new file mode 100644 index 00000000..cd962601 --- /dev/null +++ b/crates/console/prometeu-vm/tests/smoke.rs @@ -0,0 +1,29 @@ +//! Minimal, robust smoke test for the VM crate. +//! +//! Intentionally does not assert legacy ISA behavior. It only ensures that: +//! - The VM type can be instantiated without panicking. +//! - Deterministic test utilities work as expected (reproducible RNG). + +use prometeu_test_support::{next_u64, rng_from_seed}; +use prometeu_vm::VirtualMachine; + +#[test] +fn vm_instantiation_is_stable() { + // Create a VM with empty ROM and empty constant pool. + let vm = VirtualMachine::new(vec![], vec![]); + // Basic invariant checks that should remain stable across refactors. + assert_eq!(vm.pc, 0); + assert!(!vm.halted); +} + +#[test] +fn deterministic_rng_sequences_match() { + // Demonstrate deterministic behavior without relying on specific values. + let mut a = rng_from_seed(12345); + let mut b = rng_from_seed(12345); + + for _ in 0..8 { + // Same seed => same sequence + assert_eq!(next_u64(&mut a), next_u64(&mut b)); + } +} diff --git a/crates/dev/prometeu-test-support/Cargo.toml b/crates/dev/prometeu-test-support/Cargo.toml new file mode 100644 index 00000000..4e983113 --- /dev/null +++ b/crates/dev/prometeu-test-support/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "prometeu-test-support" +version = "0.1.0" +edition = "2024" +license.workspace = true + +[dependencies] +rand = { version = "0.8", features = ["std", "std_rng"] } diff --git a/crates/dev/prometeu-test-support/src/lib.rs b/crates/dev/prometeu-test-support/src/lib.rs new file mode 100644 index 00000000..b82b322d --- /dev/null +++ b/crates/dev/prometeu-test-support/src/lib.rs @@ -0,0 +1,108 @@ +//! Test-only utilities for deterministic behavior in unit tests. +//! +//! This crate is intended to be used as a dev-dependency only. It provides: +//! - Seeded RNG helpers for reproducible randomness in tests. +//! - A tiny deterministic clock abstraction for tests that need to reason about time. + +use rand::{RngCore, SeedableRng, rngs::StdRng}; + +/// Builds a `StdRng` from a u64 seed in a deterministic way. +/// +/// This expands the u64 seed into a 32-byte array (little-endian repeated) +/// to initialize `StdRng`. +pub fn rng_from_seed(seed: u64) -> StdRng { + let le = seed.to_le_bytes(); + let mut buf = [0u8; 32]; + // Repeat the 8-byte seed 4 times to fill 32 bytes + for i in 0..4 { + buf[i * 8..(i + 1) * 8].copy_from_slice(&le); + } + StdRng::from_seed(buf) +} + +/// Convenience helper that returns a RNG with a fixed well-known seed. +pub fn deterministic_rng() -> StdRng { + rng_from_seed(0xC0FFEE_5EED) +} + +/// Returns the next u64 from the provided RNG. +pub fn next_u64(rng: &mut StdRng) -> u64 { + rng.next_u64() +} + +/// Collects `n` u64 values from the RNG. +pub fn take_n_u64(rng: &mut StdRng, n: usize) -> Vec { + let mut out = Vec::with_capacity(n); + for _ in 0..n { + out.push(rng.next_u64()); + } + out +} + +/// Simple deterministic clock abstraction for tests. +pub trait Clock { + fn now_millis(&self) -> u64; +} + +/// A clock that always returns a fixed instant. +pub struct FixedClock { + now: u64, +} + +impl FixedClock { + pub fn new(now: u64) -> Self { + Self { now } + } +} + +impl Clock for FixedClock { + fn now_millis(&self) -> u64 { + self.now + } +} + +/// A clock that advances by a constant step each tick. +pub struct TickingClock { + now: u64, + step: u64, +} + +impl TickingClock { + pub fn new(start: u64, step: u64) -> Self { + Self { now: start, step } + } + pub fn tick(&mut self) { + self.now = self.now.saturating_add(self.step); + } +} + +impl Clock for TickingClock { + fn now_millis(&self) -> u64 { + self.now + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn rng_reproducible_sequences() { + let mut a = rng_from_seed(42); + let mut b = rng_from_seed(42); + + for _ in 0..10 { + assert_eq!(a.next_u64(), b.next_u64()); + } + } + + #[test] + fn ticking_clock_advances_deterministically() { + let mut c = TickingClock::new(1000, 16); + assert_eq!(c.now_millis(), 1000); + c.tick(); + assert_eq!(c.now_millis(), 1016); + c.tick(); + assert_eq!(c.now_millis(), 1032); + } +} diff --git a/crates/host/prometeu-host-desktop-winit/src/audio.rs b/crates/host/prometeu-host-desktop-winit/src/audio.rs index 39777d05..4f986d3c 100644 --- a/crates/host/prometeu-host-desktop-winit/src/audio.rs +++ b/crates/host/prometeu-host-desktop-winit/src/audio.rs @@ -1,10 +1,10 @@ use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use prometeu_drivers::{AudioCommand, Channel, MAX_CHANNELS, OUTPUT_SAMPLE_RATE}; -use ringbuf::traits::{Consumer, Producer, Split}; +use prometeu_hal::LoopMode; use ringbuf::HeapRb; +use ringbuf::traits::{Consumer, Producer, Split}; use std::sync::Arc; use std::time::Duration; -use prometeu_hal::LoopMode; pub struct HostAudio { pub producer: Option>>>, @@ -14,18 +14,12 @@ pub struct HostAudio { impl HostAudio { pub fn new() -> Self { - Self { - producer: None, - perf_consumer: None, - _stream: None, - } + Self { producer: None, perf_consumer: None, _stream: None } } pub fn init(&mut self) { let host = cpal::default_host(); - let device = host - .default_output_device() - .expect("no output device available"); + let device = host.default_output_device().expect("no output device available"); let config = cpal::StreamConfig { channels: 2, @@ -94,26 +88,17 @@ pub struct AudioMixer { impl AudioMixer { pub fn new() -> Self { - Self { - voices: Default::default(), - last_processing_time: Duration::ZERO, - paused: false, - } + Self { voices: Default::default(), last_processing_time: Duration::ZERO, paused: false } } pub fn process_command(&mut self, cmd: AudioCommand) { match cmd { - AudioCommand::Play { - sample, - voice_id, - volume, - pan, - pitch, - priority, - loop_mode, - } => { + AudioCommand::Play { sample, voice_id, volume, pan, pitch, priority, loop_mode } => { if voice_id < MAX_CHANNELS { - println!("[AudioMixer] Playing voice {}: vol={}, pitch={}, loop={:?}", voice_id, volume, pitch, loop_mode); + println!( + "[AudioMixer] Playing voice {}: vol={}, pitch={}, loop={:?}", + voice_id, volume, pitch, loop_mode + ); self.voices[voice_id] = Channel { sample: Some(sample), active: true, @@ -212,7 +197,8 @@ impl AudioMixer { voice.pos += step; - let end_pos = sample_data.loop_end.map(|e| e as f64).unwrap_or(sample_data.data.len() as f64); + let end_pos = + sample_data.loop_end.map(|e| e as f64).unwrap_or(sample_data.data.len() as f64); if voice.pos >= end_pos { if voice.loop_mode == LoopMode::On { diff --git a/crates/host/prometeu-host-desktop-winit/src/cap.rs b/crates/host/prometeu-host-desktop-winit/src/cap.rs index d4d5eb7a..2588ae9e 100644 --- a/crates/host/prometeu-host-desktop-winit/src/cap.rs +++ b/crates/host/prometeu-host-desktop-winit/src/cap.rs @@ -2,16 +2,17 @@ use prometeu_hal::telemetry::CertificationConfig; pub fn load_cap_config(path: &str) -> Option { let content = std::fs::read_to_string(path).ok()?; - let mut config = CertificationConfig { - enabled: true, - ..Default::default() - }; + let mut config = CertificationConfig { enabled: true, ..Default::default() }; for line in content.lines() { let line = line.trim(); - if line.is_empty() || line.starts_with('#') { continue; } + if line.is_empty() || line.starts_with('#') { + continue; + } let parts: Vec<&str> = line.split('=').collect(); - if parts.len() != 2 { continue; } + if parts.len() != 2 { + continue; + } let key = parts[0].trim(); let val = parts[1].trim(); diff --git a/crates/host/prometeu-host-desktop-winit/src/debugger.rs b/crates/host/prometeu-host-desktop-winit/src/debugger.rs index a29ccb44..95e24148 100644 --- a/crates/host/prometeu-host-desktop-winit/src/debugger.rs +++ b/crates/host/prometeu-host-desktop-winit/src/debugger.rs @@ -1,15 +1,15 @@ -use prometeu_hal::debugger_protocol::*; +use prometeu_drivers::hardware::Hardware; use prometeu_firmware::{BootTarget, Firmware}; +use prometeu_hal::cartridge_loader::CartridgeLoader; +use prometeu_hal::debugger_protocol::*; use std::io::{Read, Write}; use std::net::{TcpListener, TcpStream}; -use prometeu_drivers::hardware::Hardware; -use prometeu_hal::cartridge_loader::CartridgeLoader; /// Host-side implementation of the PROMETEU DevTools Protocol. -/// +/// /// This component acts as a TCP server that allows external tools (like the /// Prometeu Debugger) to observe and control the execution of the virtual machine. -/// +/// /// Communication is based on JSONL (JSON lines) over TCP. pub struct HostDebugger { /// If true, the VM will not start execution until a 'start' command is received. @@ -42,12 +42,12 @@ impl HostDebugger { if let BootTarget::Cartridge { path, debug: true, debug_port } = boot_target { self.waiting_for_start = true; - // Pre-load cartridge metadata so the Handshake message can contain + // Pre-load cartridge metadata so the Handshake message can contain // valid information about the App being debugged. if let Ok(cartridge) = CartridgeLoader::load(path) { firmware.os.initialize_vm(&mut firmware.vm, &cartridge); } - + match TcpListener::bind(format!("127.0.0.1:{}", debug_port)) { Ok(listener) => { // Set listener to non-blocking so it doesn't halt the main loop. @@ -59,7 +59,7 @@ impl HostDebugger { eprintln!("[Debugger] Failed to bind to port {}: {}", debug_port, e); } } - + println!("[Debugger] (Or press D to start execution)"); } } @@ -94,9 +94,9 @@ impl HostDebugger { if self.stream.is_none() { println!("[Debugger] Connection received!"); stream.set_nonblocking(true).expect("Cannot set non-blocking on stream"); - + self.stream = Some(stream); - + // Immediately send the Handshake message to identify the Runtime and App. let handshake = DebugResponse::Handshake { protocol_version: DEVTOOLS_PROTOCOL_VERSION, @@ -130,13 +130,15 @@ impl HostDebugger { Ok(n) => { let data = &buf[..n]; let msg = String::from_utf8_lossy(data); - + self.stream = Some(stream); - + // Support multiple JSON messages in a single TCP packet. for line in msg.lines() { let trimmed = line.trim(); - if trimmed.is_empty() { continue; } + if trimmed.is_empty() { + continue; + } if let Ok(cmd) = serde_json::from_str::(trimmed) { self.handle_command(cmd, firmware, hardware); } @@ -154,7 +156,7 @@ impl HostDebugger { } } } - + // 3. Push events (logs, telemetry) to the client. if self.stream.is_some() { self.stream_events(firmware); @@ -162,7 +164,12 @@ impl HostDebugger { } /// Dispatches a specific DebugCommand to the system components. - fn handle_command(&mut self, cmd: DebugCommand, firmware: &mut Firmware, hardware: &mut Hardware) { + fn handle_command( + &mut self, + cmd: DebugCommand, + firmware: &mut Firmware, + hardware: &mut Hardware, + ) { match cmd { DebugCommand::Ok | DebugCommand::Start => { if self.waiting_for_start { @@ -189,9 +196,8 @@ impl HostDebugger { } DebugCommand::GetState => { // Return detailed VM register and stack state. - let stack_top = firmware.vm.operand_stack.iter() - .rev().take(10).cloned().collect(); - + let stack_top = firmware.vm.operand_stack.iter().rev().take(10).cloned().collect(); + let resp = DebugResponse::GetState { pc: firmware.vm.pc, stack_top, @@ -219,13 +225,13 @@ impl HostDebugger { let new_events = firmware.os.log_service.get_after(self.last_log_seq); for event in new_events { self.last_log_seq = event.seq; - + // Map specific internal log tags to protocol events. if event.tag == 0xDEB1 { - self.send_event(DebugEvent::BreakpointHit { - pc: firmware.vm.pc, - frame_index: firmware.os.logical_frame_index, - }); + self.send_event(DebugEvent::BreakpointHit { + pc: firmware.vm.pc, + frame_index: firmware.os.logical_frame_index, + }); } // Map Certification tags (0xCA01-0xCA03) to 'Cert' protocol events. @@ -266,27 +272,27 @@ impl HostDebugger { msg: event.msg.clone(), }); } - + // 2. Send telemetry snapshots at the completion of every frame. let current_frame = firmware.os.logical_frame_index; if current_frame > self.last_telemetry_frame { - let tel = &firmware.os.telemetry_last; - self.send_event(DebugEvent::Telemetry { - frame_index: tel.frame_index, - vm_steps: tel.vm_steps, - syscalls: tel.syscalls, - cycles: tel.cycles_used, - cycles_budget: tel.cycles_budget, - host_cpu_time_us: tel.host_cpu_time_us, - violations: tel.violations, - gfx_used_bytes: tel.gfx_used_bytes, - gfx_inflight_bytes: tel.gfx_inflight_bytes, - gfx_slots_occupied: tel.gfx_slots_occupied, - audio_used_bytes: tel.audio_used_bytes, - audio_inflight_bytes: tel.audio_inflight_bytes, - audio_slots_occupied: tel.audio_slots_occupied, - }); - self.last_telemetry_frame = current_frame; + let tel = &firmware.os.telemetry_last; + self.send_event(DebugEvent::Telemetry { + frame_index: tel.frame_index, + vm_steps: tel.vm_steps, + syscalls: tel.syscalls, + cycles: tel.cycles_used, + cycles_budget: tel.cycles_budget, + host_cpu_time_us: tel.host_cpu_time_us, + violations: tel.violations, + gfx_used_bytes: tel.gfx_used_bytes, + gfx_inflight_bytes: tel.gfx_inflight_bytes, + gfx_slots_occupied: tel.gfx_slots_occupied, + audio_used_bytes: tel.audio_used_bytes, + audio_inflight_bytes: tel.audio_inflight_bytes, + audio_slots_occupied: tel.audio_slots_occupied, + }); + self.last_telemetry_frame = current_frame; } } } diff --git a/crates/host/prometeu-host-desktop-winit/src/fs_backend.rs b/crates/host/prometeu-host-desktop-winit/src/fs_backend.rs index 95de2237..06331fdd 100644 --- a/crates/host/prometeu-host-desktop-winit/src/fs_backend.rs +++ b/crates/host/prometeu-host-desktop-winit/src/fs_backend.rs @@ -1,6 +1,6 @@ +use prometeu_system::fs::{FsBackend, FsEntry, FsError}; use std::fs; use std::path::PathBuf; -use prometeu_system::fs::{FsBackend, FsEntry, FsError}; pub struct HostDirBackend { root: PathBuf, @@ -90,7 +90,11 @@ mod tests { fn get_temp_dir(name: &str) -> PathBuf { let mut path = env::temp_dir(); - path.push(format!("prometeu_host_test_{}_{}", name, std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos())); + path.push(format!( + "prometeu_host_test_{}_{}", + name, + std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos() + )); fs::create_dir_all(&path).unwrap(); path } @@ -99,14 +103,14 @@ mod tests { fn test_host_dir_backend_mount_and_dirs() { let root = get_temp_dir("mount"); let mut backend = HostDirBackend::new(root.clone()); - + backend.mount().unwrap(); - + assert!(root.join("system").is_dir()); assert!(root.join("apps").is_dir()); assert!(root.join("media").is_dir()); assert!(root.join("user").is_dir()); - + let _ = fs::remove_dir_all(root); } } diff --git a/crates/host/prometeu-host-desktop-winit/src/input.rs b/crates/host/prometeu-host-desktop-winit/src/input.rs index 91e23967..111df07b 100644 --- a/crates/host/prometeu-host-desktop-winit/src/input.rs +++ b/crates/host/prometeu-host-desktop-winit/src/input.rs @@ -1,8 +1,8 @@ +use prometeu_drivers::hardware::Hardware; +use prometeu_hal::InputSignals; use winit::event::{ElementState, MouseButton, WindowEvent}; use winit::keyboard::{KeyCode, PhysicalKey}; use winit::window::Window; -use prometeu_drivers::hardware::Hardware; -use prometeu_hal::InputSignals; pub struct HostInputHandler { pub signals: InputSignals, @@ -10,9 +10,7 @@ pub struct HostInputHandler { impl HostInputHandler { pub fn new() -> Self { - Self { - signals: InputSignals::default(), - } + Self { signals: InputSignals::default() } } pub fn handle_event(&mut self, event: &WindowEvent, window: &Window) { @@ -35,7 +33,9 @@ impl HostInputHandler { KeyCode::KeyE => self.signals.r_signal = is_down, KeyCode::KeyZ => self.signals.start_signal = is_down, - KeyCode::ShiftLeft | KeyCode::ShiftRight => self.signals.select_signal = is_down, + KeyCode::ShiftLeft | KeyCode::ShiftRight => { + self.signals.select_signal = is_down + } _ => {} } diff --git a/crates/host/prometeu-host-desktop-winit/src/lib.rs b/crates/host/prometeu-host-desktop-winit/src/lib.rs index 35a46c1d..a91c231f 100644 --- a/crates/host/prometeu-host-desktop-winit/src/lib.rs +++ b/crates/host/prometeu-host-desktop-winit/src/lib.rs @@ -1,11 +1,11 @@ pub mod audio; -pub mod runner; -pub mod fs_backend; -pub mod log_sink; -pub mod debugger; -pub mod stats; -pub mod input; pub mod cap; +pub mod debugger; +pub mod fs_backend; +pub mod input; +pub mod log_sink; +pub mod runner; +pub mod stats; pub mod utilities; use cap::load_cap_config; @@ -45,17 +45,9 @@ pub fn run() -> Result<(), Box> { let cap_config = cli.cap.as_ref().and_then(|path| load_cap_config(path)); let boot_target = if let Some(path) = cli.debug { - BootTarget::Cartridge { - path, - debug: true, - debug_port: cli.port, - } + BootTarget::Cartridge { path, debug: true, debug_port: cli.port } } else if let Some(path) = cli.run { - BootTarget::Cartridge { - path, - debug: false, - debug_port: 7777, - } + BootTarget::Cartridge { path, debug: false, debug_port: 7777 } } else { BootTarget::Hub }; diff --git a/crates/host/prometeu-host-desktop-winit/src/log_sink.rs b/crates/host/prometeu-host-desktop-winit/src/log_sink.rs index 12a5c0b3..81e052f6 100644 --- a/crates/host/prometeu-host-desktop-winit/src/log_sink.rs +++ b/crates/host/prometeu-host-desktop-winit/src/log_sink.rs @@ -35,7 +35,7 @@ impl HostConsoleSink { "[{:06}ms][{}][{}][{}] {}", event.ts_ms, event.frame, level_str, source_str, event.msg ); - + self.last_seq = Some(event.seq); } } diff --git a/crates/host/prometeu-host-desktop-winit/src/main.rs b/crates/host/prometeu-host-desktop-winit/src/main.rs index 55c2ac24..0d64aaa6 100644 --- a/crates/host/prometeu-host-desktop-winit/src/main.rs +++ b/crates/host/prometeu-host-desktop-winit/src/main.rs @@ -1,3 +1,3 @@ fn main() -> Result<(), Box> { prometeu_host_desktop_winit::run() -} \ No newline at end of file +} diff --git a/crates/host/prometeu-host-desktop-winit/src/runner.rs b/crates/host/prometeu-host-desktop-winit/src/runner.rs index a088b039..97e69f7f 100644 --- a/crates/host/prometeu-host-desktop-winit/src/runner.rs +++ b/crates/host/prometeu-host-desktop-winit/src/runner.rs @@ -7,7 +7,11 @@ use crate::stats::HostStats; use crate::utilities::draw_rgb565_to_rgba8; use pixels::wgpu::PresentMode; use pixels::{Pixels, PixelsBuilder, SurfaceTexture}; +use prometeu_drivers::AudioCommand; +use prometeu_drivers::hardware::Hardware; use prometeu_firmware::{BootTarget, Firmware}; +use prometeu_hal::color::Color; +use prometeu_hal::telemetry::CertificationConfig; use std::time::{Duration, Instant}; use winit::application::ApplicationHandler; use winit::dpi::LogicalSize; @@ -15,14 +19,10 @@ use winit::event::{ElementState, WindowEvent}; use winit::event_loop::{ActiveEventLoop, ControlFlow}; use winit::keyboard::{KeyCode, PhysicalKey}; use winit::window::{Window, WindowAttributes, WindowId}; -use prometeu_hal::telemetry::CertificationConfig; -use prometeu_drivers::hardware::Hardware; -use prometeu_drivers::AudioCommand; -use prometeu_hal::color::Color; /// The Desktop implementation of the PROMETEU Runtime. -/// -/// This struct acts as the physical "chassis" of the virtual console. It is +/// +/// This struct acts as the physical "chassis" of the virtual console. It is /// responsible for: /// - Creating and managing the OS window (via `winit`). /// - Initializing the GPU-accelerated framebuffer (via `pixels`). @@ -130,8 +130,18 @@ impl HostRunner { let color_warn = Color::RED; self.hardware.gfx.fill_rect(5, 5, 175, 100, color_bg); - self.hardware.gfx.draw_text(10, 10, &format!("FPS: {:.1}", self.stats.current_fps), color_text); - self.hardware.gfx.draw_text(10, 18, &format!("HOST: {:.2}MS", tel.host_cpu_time_us as f64 / 1000.0), color_text); + self.hardware.gfx.draw_text( + 10, + 10, + &format!("FPS: {:.1}", self.stats.current_fps), + color_text, + ); + self.hardware.gfx.draw_text( + 10, + 18, + &format!("HOST: {:.2}MS", tel.host_cpu_time_us as f64 / 1000.0), + color_text, + ); self.hardware.gfx.draw_text(10, 26, &format!("STEPS: {}", tel.vm_steps), color_text); self.hardware.gfx.draw_text(10, 34, &format!("SYSC: {}", tel.syscalls), color_text); @@ -140,25 +150,60 @@ impl HostRunner { } else { 0.0 }; - self.hardware.gfx.draw_text(10, 42, &format!("CYC: {}/{} ({:.1}%)", tel.cycles_used, tel.cycles_budget, cycles_pct), color_text); + self.hardware.gfx.draw_text( + 10, + 42, + &format!("CYC: {}/{} ({:.1}%)", tel.cycles_used, tel.cycles_budget, cycles_pct), + color_text, + ); - self.hardware.gfx.draw_text(10, 50, &format!("GFX: {}K/16M ({}S)", tel.gfx_used_bytes / 1024, tel.gfx_slots_occupied), color_text); + self.hardware.gfx.draw_text( + 10, + 50, + &format!("GFX: {}K/16M ({}S)", tel.gfx_used_bytes / 1024, tel.gfx_slots_occupied), + color_text, + ); if tel.gfx_inflight_bytes > 0 { - self.hardware.gfx.draw_text(10, 58, &format!("LOAD GFX: {}KB", tel.gfx_inflight_bytes / 1024), color_warn); + self.hardware.gfx.draw_text( + 10, + 58, + &format!("LOAD GFX: {}KB", tel.gfx_inflight_bytes / 1024), + color_warn, + ); } - self.hardware.gfx.draw_text(10, 66, &format!("AUD: {}K/32M ({}S)", tel.audio_used_bytes / 1024, tel.audio_slots_occupied), color_text); + self.hardware.gfx.draw_text( + 10, + 66, + &format!("AUD: {}K/32M ({}S)", tel.audio_used_bytes / 1024, tel.audio_slots_occupied), + color_text, + ); if tel.audio_inflight_bytes > 0 { - self.hardware.gfx.draw_text(10, 74, &format!("LOAD AUD: {}KB", tel.audio_inflight_bytes / 1024), color_warn); + self.hardware.gfx.draw_text( + 10, + 74, + &format!("LOAD AUD: {}KB", tel.audio_inflight_bytes / 1024), + color_warn, + ); } let cert_color = if tel.violations > 0 { color_warn } else { color_text }; self.hardware.gfx.draw_text(10, 82, &format!("CERT LAST: {}", tel.violations), cert_color); if tel.violations > 0 { - if let Some(event) = self.firmware.os.log_service.get_recent(10).into_iter().rev().find(|e| e.tag >= 0xCA01 && e.tag <= 0xCA03) { + if let Some(event) = self + .firmware + .os + .log_service + .get_recent(10) + .into_iter() + .rev() + .find(|e| e.tag >= 0xCA01 && e.tag <= 0xCA03) + { let mut msg = event.msg.clone(); - if msg.len() > 30 { msg.truncate(30); } + if msg.len() > 30 { + msg.truncate(30); + } self.hardware.gfx.draw_text(10, 90, &msg, color_warn); } } @@ -183,11 +228,12 @@ impl ApplicationHandler for HostRunner { let size = window.inner_size(); let surface_texture = SurfaceTexture::new(size.width, size.height, window); - let mut pixels = PixelsBuilder::new(Hardware::W as u32, Hardware::H as u32, surface_texture) - .present_mode(PresentMode::Fifo) // activate vsync - .build() - .expect("failed to create Pixels"); - + let mut pixels = + PixelsBuilder::new(Hardware::W as u32, Hardware::H as u32, surface_texture) + .present_mode(PresentMode::Fifo) // activate vsync + .build() + .expect("failed to create Pixels"); + pixels.frame_mut().fill(0); self.pixels = Some(pixels); @@ -197,7 +243,6 @@ impl ApplicationHandler for HostRunner { event_loop.set_control_flow(ControlFlow::Poll); } - fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { self.input.handle_event(&event, self.window()); @@ -232,7 +277,6 @@ impl ApplicationHandler for HostRunner { } } - WindowEvent::KeyboardInput { event, .. } => { if let PhysicalKey::Code(code) = event.physical_key { let is_down = event.state == ElementState::Pressed; @@ -270,13 +314,13 @@ impl ApplicationHandler for HostRunner { } // 3. Timing Management (The heart of determinism). - // We measure the elapsed time since the last iteration and add it to an - // accumulator. We then execute exactly as many 60Hz slices as the + // We measure the elapsed time since the last iteration and add it to an + // accumulator. We then execute exactly as many 60Hz slices as the // accumulator allows. let now = Instant::now(); let mut frame_delta = now.duration_since(self.last_frame_time); - // Safety cap: if the OS freezes or we fall behind too much, we don't try + // Safety cap: if the OS freezes or we fall behind too much, we don't try // to catch up indefinitely (avoiding the "death spiral"). if frame_delta > Duration::from_millis(100) { frame_delta = Duration::from_millis(100); @@ -293,16 +337,13 @@ impl ApplicationHandler for HostRunner { } // Sync pause state with audio. - // We do this AFTER firmware.tick to avoid MasterPause/Resume commands + // We do this AFTER firmware.tick to avoid MasterPause/Resume commands // being cleared by the OS if a new logical frame starts in this tick. let is_paused = self.firmware.os.paused || self.debugger.waiting_for_start; if is_paused != self.last_paused_state { self.last_paused_state = is_paused; - let cmd = if is_paused { - AudioCommand::MasterPause - } else { - AudioCommand::MasterResume - }; + let cmd = + if is_paused { AudioCommand::MasterPause } else { AudioCommand::MasterResume }; self.hardware.audio.commands.push(cmd); } @@ -331,7 +372,7 @@ impl ApplicationHandler for HostRunner { // 5. Rendering the Telemetry Overlay (if enabled). if self.overlay_enabled { // We temporarily swap buffers to draw over the current image. - self.hardware.gfx.present(); + self.hardware.gfx.present(); self.display_dbg_overlay(); self.hardware.gfx.present(); } @@ -345,9 +386,9 @@ impl ApplicationHandler for HostRunner { mod tests { use super::*; use prometeu_firmware::BootTarget; + use prometeu_hal::debugger_protocol::DEVTOOLS_PROTOCOL_VERSION; use std::io::{Read, Write}; use std::net::TcpStream; - use prometeu_hal::debugger_protocol::DEVTOOLS_PROTOCOL_VERSION; #[test] fn test_debug_port_opens() { @@ -364,10 +405,11 @@ mod tests { // Check if we can connect { - let mut stream = TcpStream::connect(format!("127.0.0.1:{}", port)).expect("Should connect"); + let mut stream = + TcpStream::connect(format!("127.0.0.1:{}", port)).expect("Should connect"); // Short sleep to ensure the OS processes std::thread::sleep(std::time::Duration::from_millis(100)); - + // Simulates the loop to accept the connection runner.debugger.check_commands(&mut runner.firmware, &mut runner.hardware); assert!(runner.debugger.stream.is_some(), "Stream should have been kept open"); @@ -375,24 +417,36 @@ mod tests { // Handshake Check let mut buf = [0u8; 2048]; let n = stream.read(&mut buf).expect("Should read handshake"); - let resp: serde_json::Value = serde_json::from_slice(&buf[..n]).expect("Handshake should be valid JSON"); + let resp: serde_json::Value = + serde_json::from_slice(&buf[..n]).expect("Handshake should be valid JSON"); assert_eq!(resp["type"], "handshake"); assert_eq!(resp["protocol_version"], DEVTOOLS_PROTOCOL_VERSION); // Send start via JSON - stream.write_all(b"{\"type\":\"start\"}\n").expect("Connection should be open for writing"); + stream + .write_all(b"{\"type\":\"start\"}\n") + .expect("Connection should be open for writing"); std::thread::sleep(std::time::Duration::from_millis(50)); - + // Process the received command runner.debugger.check_commands(&mut runner.firmware, &mut runner.hardware); - assert!(!runner.debugger.waiting_for_start, "Execution should have started after start command"); - assert!(runner.debugger.listener.is_some(), "Listener should remain open for reconnections"); + assert!( + !runner.debugger.waiting_for_start, + "Execution should have started after start command" + ); + assert!( + runner.debugger.listener.is_some(), + "Listener should remain open for reconnections" + ); } - + // Now that the stream is out of the test scope, the runner should detect closure on next check std::thread::sleep(std::time::Duration::from_millis(50)); runner.debugger.check_commands(&mut runner.firmware, &mut runner.hardware); - assert!(runner.debugger.stream.is_none(), "Stream should have been closed after client disconnected"); + assert!( + runner.debugger.stream.is_none(), + "Stream should have been closed after client disconnected" + ); } #[test] @@ -407,7 +461,8 @@ mod tests { // 1. Connect and start { - let mut stream = TcpStream::connect(format!("127.0.0.1:{}", port)).expect("Should connect 1"); + let mut stream = + TcpStream::connect(format!("127.0.0.1:{}", port)).expect("Should connect 1"); std::thread::sleep(std::time::Duration::from_millis(50)); runner.debugger.check_commands(&mut runner.firmware, &mut runner.hardware); assert!(runner.debugger.stream.is_some()); @@ -427,10 +482,13 @@ mod tests { // 3. Try to reconnect - SHOULD FAIL currently, but we want it to WORK let stream2 = TcpStream::connect(format!("127.0.0.1:{}", port)); assert!(stream2.is_ok(), "Should accept new connection even after start"); - + std::thread::sleep(std::time::Duration::from_millis(50)); runner.debugger.check_commands(&mut runner.firmware, &mut runner.hardware); - assert!(runner.debugger.stream.is_some(), "Stream should have been accepted on reconnection"); + assert!( + runner.debugger.stream.is_some(), + "Stream should have been accepted on reconnection" + ); } #[test] @@ -444,22 +502,27 @@ mod tests { }); // 1. First connection - let mut _stream1 = TcpStream::connect(format!("127.0.0.1:{}", port)).expect("Should connect 1"); + let mut _stream1 = + TcpStream::connect(format!("127.0.0.1:{}", port)).expect("Should connect 1"); std::thread::sleep(std::time::Duration::from_millis(50)); runner.debugger.check_commands(&mut runner.firmware, &mut runner.hardware); assert!(runner.debugger.stream.is_some()); // 2. Second connection - let mut stream2 = TcpStream::connect(format!("127.0.0.1:{}", port)).expect("Should connect 2 (OS accepts)"); + let mut stream2 = TcpStream::connect(format!("127.0.0.1:{}", port)) + .expect("Should connect 2 (OS accepts)"); std::thread::sleep(std::time::Duration::from_millis(50)); runner.debugger.check_commands(&mut runner.firmware, &mut runner.hardware); // Should accept and close stream2 - + // Check if stream2 was closed by the server let mut buf = [0u8; 10]; stream2.set_read_timeout(Some(std::time::Duration::from_millis(100))).unwrap(); let res = stream2.read(&mut buf); - assert!(matches!(res, Ok(0)) || res.is_err(), "Second connection should be closed by server"); - + assert!( + matches!(res, Ok(0)) || res.is_err(), + "Second connection should be closed by server" + ); + assert!(runner.debugger.stream.is_some(), "First connection should continue active"); } @@ -479,7 +542,7 @@ mod tests { use std::io::BufRead; let mut reader = std::io::BufReader::new(stream); - + let mut line = String::new(); reader.read_line(&mut line).expect("Should read handshake"); assert!(line.contains("handshake")); @@ -487,15 +550,17 @@ mod tests { // Send getState reader.get_mut().write_all(b"{\"type\":\"getState\"}\n").expect("Should write getState"); std::thread::sleep(std::time::Duration::from_millis(100)); - + runner.debugger.check_commands(&mut runner.firmware, &mut runner.hardware); // Check if response received (may have events/logs before) loop { line.clear(); reader.read_line(&mut line).expect("Should read line"); - if line.is_empty() { break; } - + if line.is_empty() { + break; + } + if let Ok(resp) = serde_json::from_str::(&line) { if resp["type"] == "getState" { return; @@ -517,10 +582,11 @@ mod tests { // 1. Connect and pause { - let mut stream = TcpStream::connect(format!("127.0.0.1:{}", port)).expect("Should connect"); + let mut stream = + TcpStream::connect(format!("127.0.0.1:{}", port)).expect("Should connect"); std::thread::sleep(std::time::Duration::from_millis(50)); runner.debugger.check_commands(&mut runner.firmware, &mut runner.hardware); - + stream.write_all(b"{\"type\":\"pause\"}\n").expect("Should write pause"); std::thread::sleep(std::time::Duration::from_millis(50)); runner.debugger.check_commands(&mut runner.firmware, &mut runner.hardware); @@ -530,7 +596,7 @@ mod tests { // 2. Disconnect (stream goes out of scope) std::thread::sleep(std::time::Duration::from_millis(50)); runner.debugger.check_commands(&mut runner.firmware, &mut runner.hardware); - + // 3. Check if unpaused assert!(!runner.firmware.os.paused, "VM should have unpaused after disconnect"); assert!(!runner.debugger.waiting_for_start, "VM should have left waiting_for_start state"); diff --git a/crates/host/prometeu-host-desktop-winit/src/stats.rs b/crates/host/prometeu-host-desktop-winit/src/stats.rs index 906e7231..e62190bc 100644 --- a/crates/host/prometeu-host-desktop-winit/src/stats.rs +++ b/crates/host/prometeu-host-desktop-winit/src/stats.rs @@ -1,7 +1,7 @@ +use prometeu_drivers::hardware::Hardware; use prometeu_firmware::Firmware; use std::time::{Duration, Instant}; use winit::window::Window; -use prometeu_drivers::hardware::Hardware; pub struct HostStats { pub last_stats_update: Instant, @@ -31,15 +31,22 @@ impl HostStats { self.audio_load_samples += 1; } - pub fn update(&mut self, now: Instant, window: Option<&Window>, _hardware: &Hardware, firmware: &Firmware) { + pub fn update( + &mut self, + now: Instant, + window: Option<&Window>, + _hardware: &Hardware, + firmware: &Firmware, + ) { let stats_elapsed = now.duration_since(self.last_stats_update); if stats_elapsed >= Duration::from_secs(1) { self.current_fps = self.frames_since_last_update as f64 / stats_elapsed.as_secs_f64(); - + if let Some(window) = window { // Fixed comparison always against 60Hz, keep even when doing CPU stress tests let frame_budget_us = 16666.0; - let cpu_load_core = (firmware.os.last_frame_cpu_time_us as f64 / frame_budget_us) * 100.0; + let cpu_load_core = + (firmware.os.last_frame_cpu_time_us as f64 / frame_budget_us) * 100.0; let cpu_load_audio = if self.audio_load_samples > 0 { (self.audio_load_accum_us as f64 / stats_elapsed.as_micros() as f64) * 100.0 @@ -49,7 +56,12 @@ impl HostStats { let title = format!( "PROMETEU | GFX: {:.1} KB | FPS: {:.1} | Load: {:.1}% (C) + {:.1}% (A) | Frame: tick {} logical {}", - 0, self.current_fps, cpu_load_core, cpu_load_audio, firmware.os.tick_index, firmware.os.logical_frame_index + 0, + self.current_fps, + cpu_load_core, + cpu_load_audio, + firmware.os.tick_index, + firmware.os.logical_frame_index ); window.set_title(&title); } diff --git a/crates/tools/prometeu-cli/src/bin/prometeu-runtime.rs b/crates/tools/prometeu-cli/src/bin/prometeu-runtime.rs index f13bd332..0d64aaa6 100644 --- a/crates/tools/prometeu-cli/src/bin/prometeu-runtime.rs +++ b/crates/tools/prometeu-cli/src/bin/prometeu-runtime.rs @@ -1 +1,3 @@ -fn main() -> Result<(), Box> { prometeu_host_desktop_winit::run() } +fn main() -> Result<(), Box> { + prometeu_host_desktop_winit::run() +} diff --git a/crates/tools/prometeu-cli/src/main.rs b/crates/tools/prometeu-cli/src/main.rs index 7a9da20f..bbd42e7a 100644 --- a/crates/tools/prometeu-cli/src/main.rs +++ b/crates/tools/prometeu-cli/src/main.rs @@ -4,10 +4,10 @@ use std::path::{Path, PathBuf}; use std::process::Command; /// PROMETEU Dispatcher (CLI). -/// -/// The main entry point for the user. This binary does not implement -/// compilation or execution logic itself; instead, it acts as a smart -/// front-end that locates and dispatches commands to specialized +/// +/// The main entry point for the user. This binary does not implement +/// compilation or execution logic itself; instead, it acts as a smart +/// front-end that locates and dispatches commands to specialized /// components like `prometeu-host-desktop-winit` or `prometeu-build-pipeline`. #[derive(Parser)] #[command(name = "prometeu")] @@ -141,7 +141,8 @@ fn dispatch(exe_dir: &Path, bin_name: &str, args: &[&str]) { "prometeuc" => "build/verify c", "prometeup" => "pack/verify p", _ => bin_name, - }, exe_dir.display() + }, + exe_dir.display() ); std::process::exit(1); } @@ -168,9 +169,6 @@ fn execute_bin(bin_path: &Path, args: &[&str]) { } fn not_implemented(cmd: &str, _bin_name: &str) { - eprintln!( - "prometeu: command '{}' is not yet available in this distribution", - cmd - ); + eprintln!("prometeu: command '{}' is not yet available in this distribution", cmd); std::process::exit(1); } diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 00000000..97f03d62 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,25 @@ +# Prometeu Runtime โ€” Architecture (Reset Invariants) + +This document captures the high-level invariants for the reset cycle. It is a stub and will +evolve as we formalize the new ISA/VM specs. The goal is to define non-negotiable properties +that guide refactors without forcing legacy compatibility. + +Core invariants +--------------- +- Stack-based VM with heap-allocated objects. +- Garbage Collection happens at safepoints, primarily at `FRAME_SYNC`. +- Closures are first-class (user functions). Syscalls are callable but not first-class values. +- Coroutines are the only concurrency model. +- No backward compatibility: old bytecode formats, shims, or legacy bridges are out of scope. + +Scope of this stage +------------------- +- Establish tooling baselines (fmt, clippy, CI) and minimal smoke tests. +- Avoid encoding legacy ISA semantics in tests; keep tests focused on build confidence. +- Production code must remain free from test-only hooks; use dev-only utilities for determinism. + +Out of scope (for now) +---------------------- +- Detailed ISA definition and instruction semantics beyond what is needed to compile and run + smoke-level validations. +- Performance tuning or GC algorithm selection. diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 915003f5..00000000 --- a/docs/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# PROMETEU Documentation - -This directory contains the technical documentation and specifications for the PROMETEU project. - -## Content - -### ๐Ÿ“œ [Specifications (Specs)](./specs) -Detailed documentation on system architecture, cartridge format, VM instruction set, and more. -- [Topic Index](specs/hardware/topics/table-of-contents.md) - -### ๐Ÿž [Debugger](./debugger) -Documentation on debugging tools and how to integrate new tools into the ecosystem. - -### ๐Ÿ”Œ [DevTools Protocol](../devtools) -Definition of the communication protocol used for real-time debugging and inspection. diff --git a/docs/STYLE.md b/docs/STYLE.md new file mode 100644 index 00000000..42ea0168 --- /dev/null +++ b/docs/STYLE.md @@ -0,0 +1,49 @@ +# Prometeu Runtime โ€” Style Guide + +This document defines the baseline code style and commenting policy for the reset cycle. + +Goals: +- Professional, consistent, and readable code. +- Deterministic and actionable error messages. +- English-only comments and docs. + +General principles +------------------ +- Rust edition: use the crateโ€™s configured edition (currently 2024 across new crates). +- Keep modules small and cohesive; prefer explicit interfaces over glob imports. +- Avoid premature abstraction; refactor when duplication becomes meaningful. + +Comments and documentation +-------------------------- +- Language: English only. +- Use `///` for public API documentation comments and module-level docs (`//!`). +- Use `//` for local/internal notes that do not need to surface in docs. +- Write comments that explain โ€œwhy,โ€ not โ€œwhatโ€ (the code already shows โ€œwhatโ€). +- Keep comments up to date with behavior; outdated comments should be removed or fixed. + +Error messages +-------------- +- Be clear, actionable, and deterministic. +- Include the failing condition and the expected invariant when useful. +- Avoid leaking internal jargon; prefer user-understandable phrasing. +- Do not rely on timing or nondeterminism for error reproduction. + +Naming conventions +------------------ +- Crates and modules: `kebab-case` for crates, `snake_case` for modules and files. +- Types and traits: `PascalCase` (e.g., `VirtualMachine`, `BytecodeLoader`). +- Functions, methods, and variables: `snake_case`. +- Constants and statics: `SCREAMING_SNAKE_CASE`. +- Avoid abbreviations unless they are widely recognized (e.g., `pc`, `vm`). + +Documentation structure +----------------------- +- Each public crate should have a crate-level docs section describing its purpose. +- Prefer small examples in docs that compile (use `rustdoc` code blocks when feasible). +- Keep module/file headers brief and focused. + +Formatting and linting +---------------------- +- `cargo fmt` is the source of truth for formatting. +- `cargo clippy` should run clean on the default toolchain. Where a lint is intentionally + violated, add a focused `#[allow(lint_name)]` with a short rationale. diff --git a/docs/phase-03-frontend-api.md b/docs/phase-03-frontend-api.md deleted file mode 100644 index 7c3950fa..00000000 --- a/docs/phase-03-frontend-api.md +++ /dev/null @@ -1,19 +0,0 @@ -### Phase 03 โ€” Frontend API Boundary (Canon Contract) - -This document codifies the FE/BE boundary invariants for Phase 03. - -- BE is the source of truth. The `frontend-api` crate defines canonical models that all Frontends must produce. -- No string protocols across layers. Strings are only for display/debug. No hidden prefixes like `svc:` or `@dep:`. -- No FE implementation imports from other FE implementations. -- No BE imports PBS modules (hard boundary). The Backend consumes only canonical data structures from `frontend-api`. -- Overload resolution is signature-based. Arity alone is not sufficient; use canonical signatures/keys. - -Implementation notes (PBS): - -- The PBS adapter must not synthesize ownership or module info from string prefixes. All owner/module data should come from canonical types. -- Export/import surfaces are expressed exclusively via `frontend-api` types (e.g., `ItemName`, `ProjectAlias`, `ModulePath`, `ImportRef`, `ExportItem`). - -Enforcement: - -- A test in `prometeu-build-pipeline` scans `src/backend/**` to ensure no references to `frontends::pbs` are introduced. -- Code review should reject any PRs that reintroduce prefix-based heuristics or FE-to-FE coupling. diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..fd2ae1c6 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,3 @@ +max_width = 100 +use_small_heuristics = "Max" +newline_style = "Unix"