diff --git a/crates/prometeu-core/src/hardware/syscalls.rs b/crates/prometeu-core/src/hardware/syscalls.rs index 4a9234c9..94e4c129 100644 --- a/crates/prometeu-core/src/hardware/syscalls.rs +++ b/crates/prometeu-core/src/hardware/syscalls.rs @@ -212,4 +212,49 @@ impl Syscall { _ => 1, } } + + pub fn name(&self) -> &'static str { + match self { + Self::SystemHasCart => "SystemHasCart", + Self::SystemRunCart => "SystemRunCart", + Self::GfxClear => "GfxClear", + Self::GfxFillRect => "GfxFillRect", + Self::GfxDrawLine => "GfxDrawLine", + Self::GfxDrawCircle => "GfxDrawCircle", + Self::GfxDrawDisc => "GfxDrawDisc", + Self::GfxDrawSquare => "GfxDrawSquare", + Self::GfxSetSprite => "GfxSetSprite", + Self::GfxDrawText => "GfxDrawText", + Self::GfxClear565 => "GfxClear565", + Self::InputGetPad => "InputGetPad", + Self::InputGetPadPressed => "InputGetPadPressed", + Self::InputGetPadReleased => "InputGetPadReleased", + Self::InputGetPadHold => "InputGetPadHold", + Self::InputPadSnapshot => "InputPadSnapshot", + Self::InputTouchSnapshot => "InputTouchSnapshot", + Self::TouchGetX => "TouchGetX", + Self::TouchGetY => "TouchGetY", + Self::TouchIsDown => "TouchIsDown", + Self::TouchIsPressed => "TouchIsPressed", + Self::TouchIsReleased => "TouchIsReleased", + Self::TouchGetHold => "TouchGetHold", + Self::AudioPlaySample => "AudioPlaySample", + Self::AudioPlay => "AudioPlay", + Self::FsOpen => "FsOpen", + Self::FsRead => "FsRead", + Self::FsWrite => "FsWrite", + Self::FsClose => "FsClose", + Self::FsListDir => "FsListDir", + Self::FsExists => "FsExists", + Self::FsDelete => "FsDelete", + Self::LogWrite => "LogWrite", + Self::LogWriteTag => "LogWriteTag", + Self::AssetLoad => "AssetLoad", + Self::AssetStatus => "AssetStatus", + Self::AssetCommit => "AssetCommit", + Self::AssetCancel => "AssetCancel", + Self::BankInfo => "BankInfo", + Self::BankSlotInfo => "BankSlotInfo", + } + } } diff --git a/crates/prometeu-core/src/virtual_machine/verifier.rs b/crates/prometeu-core/src/virtual_machine/verifier.rs index ba63820d..38530297 100644 --- a/crates/prometeu-core/src/virtual_machine/verifier.rs +++ b/crates/prometeu-core/src/virtual_machine/verifier.rs @@ -311,4 +311,19 @@ mod tests { let res = Verifier::verify(&code, &functions).unwrap(); assert_eq!(res[0], 2); } + + #[test] + fn test_verifier_invalid_syscall_id() { + let mut code = Vec::new(); + 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 res = Verifier::verify(&code, &functions); + assert_eq!(res, Err(VerifierError::InvalidSyscallId { pc: 0, id: 0xDEADBEEF })); + } } diff --git a/crates/prometeu-core/src/virtual_machine/virtual_machine.rs b/crates/prometeu-core/src/virtual_machine/virtual_machine.rs index a4535152..050a3373 100644 --- a/crates/prometeu-core/src/virtual_machine/virtual_machine.rs +++ b/crates/prometeu-core/src/virtual_machine/virtual_machine.rs @@ -999,6 +999,7 @@ impl VirtualMachine { } args.reverse(); + let stack_height_before = self.operand_stack.len(); let mut ret = crate::virtual_machine::HostReturn::new(&mut self.operand_stack); native.syscall(id, &args, &mut ret, hw).map_err(|fault| match fault { crate::virtual_machine::VmFault::Trap(code, msg) => LogicalFrameEndingReason::Trap(TrapInfo { @@ -1009,6 +1010,15 @@ impl VirtualMachine { }), crate::virtual_machine::VmFault::Panic(msg) => LogicalFrameEndingReason::Panic(msg), })?; + + let stack_height_after = self.operand_stack.len(); + let results_pushed = stack_height_after - stack_height_before; + 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 + ))); + } } OpCode::FrameSync => { return Ok(()); @@ -1929,6 +1939,37 @@ mod tests { } } + #[test] + fn test_syscall_results_count_mismatch_panic() { + // GfxClear565 (0x1010) expects 0 results + let rom = vec![ + 0x17, 0x00, // PushI32 + 0x00, 0x00, 0x00, 0x00, // value 0 + 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, _hw: &mut dyn HardwareBridge) -> Result<(), VmFault> { + // Wrong: GfxClear565 is void but we push something + ret.push_int(42); + Ok(()) + } + } + + let mut vm = VirtualMachine::new(rom, vec![]); + let mut native = BadNative; + let mut hw = MockHardware; + + vm.prepare_call("0"); + let report = vm.run_budget(100, &mut native, &mut hw).unwrap(); + match report.reason { + LogicalFrameEndingReason::Panic(msg) => assert!(msg.contains("results mismatch")), + _ => panic!("Expected Panic, got {:?}", report.reason), + } + } + #[test] fn test_host_return_bounded_overflow_trap() { let mut stack = Vec::new(); diff --git a/docs/specs/pbs/Runtime Traps.md b/docs/specs/pbs/Runtime Traps.md new file mode 100644 index 00000000..f3000ae1 --- /dev/null +++ b/docs/specs/pbs/Runtime Traps.md @@ -0,0 +1,173 @@ +# Runtime Traps v0 — Prometeu VM Specification + +> **Status:** Proposed (requires explicit owner approval) +> +> **Scope:** Prometeu VM / PBS v0 execution model + +--- + +## 1. Motivation + +Prometeu aims to be a **deterministic, industrial-grade virtual machine**. +To achieve this, execution errors that are: + +* caused by **user programs**, +* predictable by the execution model, +* and recoverable at the tooling / host level, + +must be **explicitly represented** and **ABI-stable**. + +This specification introduces **Runtime Traps** as a *formal concept*, consolidating behavior that already existed implicitly in the VM. + +--- + +## 2. Definition + +A **Runtime Trap** is a **controlled interruption of program execution** caused by a semantic violation detected at runtime. + +A trap: + +* **terminates the current execution frame** (or program, depending on host policy) +* **does not corrupt VM state** +* **returns structured diagnostic information** (`TrapInfo`) +* **is deterministic** for a given bytecode + state + +A trap is **not**: + +* a debugger breakpoint +* undefined behavior +* a VM panic +* a verifier/load-time error + +--- + +## 3. Trap vs Other Failure Modes + +| Category | When | Recoverable | ABI-stable | Example | +| ------------------ | ---------------------- | ----------- | ---------- | -------------------------------- | +| **Verifier error** | Load-time | ❌ | ❌ | Stack underflow, bad CFG join | +| **Runtime trap** | Execution | ✅ | ✅ | OOB access, invalid local | +| **VM panic** | VM invariant violation | ❌ | ❌ | Handler returns wrong slot count | +| **Breakpoint** | Debug only | ✅ | ❌ | Developer inspection | + +--- + +## 4. Trap Information (`TrapInfo`) + +All runtime traps must produce a `TrapInfo` structure with the following fields: + +```text +TrapInfo { + code: u32, // ABI-stable trap code + opcode: u16, // opcode that triggered the trap + pc: u32, // program counter (relative to module) + message: String, // human-readable explanation (non-ABI) +} +``` + +### ABI Guarantees + +* `code`, `opcode`, and `pc` are ABI-relevant and stable +* `message` is diagnostic only and may change + +--- + +## 5. Standard Trap Codes (v0) + +### 5.1 Memory & Bounds + +| Code | Name | Meaning | +| -------------------- | ------------- | ------------------------------ | +| `TRAP_OOB` | Out of bounds | Access beyond allowed bounds | +| `TRAP_INVALID_LOCAL` | Invalid local | Local slot index out of bounds | + +### 5.2 Heap / Gate + +| Code | Name | Meaning | +| ------------------- | -------------- | -------------------------- | +| `TRAP_INVALID_GATE` | Invalid gate | Non-existent gate handle | +| `TRAP_DEAD_GATE` | Dead gate | Gate with refcount = 0 | +| `TRAP_TYPE` | Type violation | Heap or gate type mismatch | + +### 5.3 System + +| Code | Name | Meaning | +| ---------------------- | --------------- | --------------------------------------- | +| `TRAP_INVALID_SYSCALL` | Invalid syscall | Unknown syscall ID | +| `TRAP_STACK_UNDERFLOW` | Stack underflow | Missing arguments for syscall or opcode | + +> This list is **closed for PBS v0** unless explicitly extended. + +--- + +## 6. Trap Semantics + +### 6.1 Execution + +When a trap occurs: + +1. The current instruction **does not complete** +2. No partial side effects are committed +3. Execution stops and returns `TrapInfo` to the host + +### 6.2 Stack & Frames + +* Operand stack is left in a **valid but unspecified** state +* Call frames above the trapping frame are not resumed + +### 6.3 Host Policy + +The host decides: + +* whether the trap terminates the whole program +* whether execution may be restarted +* how the trap is surfaced to the user (error, log, UI, etc.) + +--- + +## 7. Verifier Interaction + +The verifier **must prevent** traps that are statically provable, including: + +* stack underflow +* invalid control-flow joins +* invalid syscall IDs +* incorrect return slot counts + +If a verifier rejects a module, **no runtime traps should occur for those causes**. + +--- + +## 8. What Is *Not* a Trap + +The following are **VM bugs or tooling errors**, not traps: + +* handler returns wrong number of slots +* opcode implementation violates `OpcodeSpec` +* verifier and runtime disagree on stack effects + +These must result in **VM panic**, not a trap. + +--- + +## 9. Versioning Policy + +* Trap codes are **ABI-stable within a major version** (v0) +* New trap codes may only be added in a **new major ABI version** (v1) +* Removing or reinterpreting trap codes is forbidden + +--- + +## 10. Summary + +Runtime traps are: + +* an explicit part of the Prometeu execution model +* deterministic and ABI-stable +* reserved for **user-program semantic errors** + +They are **not** debugging tools and **not** VM panics. + +This spec formalizes existing behavior and freezes it for PBS v0. + +--- diff --git a/docs/specs/pbs/files/PRs para Junie.md b/docs/specs/pbs/files/PRs para Junie.md index c63d44aa..b74f4a57 100644 --- a/docs/specs/pbs/files/PRs para Junie.md +++ b/docs/specs/pbs/files/PRs para Junie.md @@ -1,26 +1,3 @@ -## PR-09 — Debug info v0: spans, symbols, and traceable traps - -**Why:** Industrial debugging requires actionable failures. - -### Scope - -* Add optional debug section: - - * per-instruction span table (`pc -> (file_id, start, end)`) - * function names -* Enhance trap payload with debug span (if present) - -### Tests - -* trap includes span when debug present -* trap still works without debug - -### Acceptance - -* You can pinpoint “where” a trap happened reliably. - ---- - ## PR-10 — Program image + linker: imports/exports resolved before VM run **Why:** Imports are compile-time, but we need an industrial linking model for multi-module PBS.