pr 48
This commit is contained in:
parent
33908aa828
commit
be1c244b5a
@ -212,4 +212,49 @@ impl Syscall {
|
|||||||
_ => 1,
|
_ => 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",
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -311,4 +311,19 @@ mod tests {
|
|||||||
let res = Verifier::verify(&code, &functions).unwrap();
|
let res = Verifier::verify(&code, &functions).unwrap();
|
||||||
assert_eq!(res[0], 2);
|
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 }));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -999,6 +999,7 @@ impl VirtualMachine {
|
|||||||
}
|
}
|
||||||
args.reverse();
|
args.reverse();
|
||||||
|
|
||||||
|
let stack_height_before = self.operand_stack.len();
|
||||||
let mut ret = crate::virtual_machine::HostReturn::new(&mut self.operand_stack);
|
let mut ret = crate::virtual_machine::HostReturn::new(&mut self.operand_stack);
|
||||||
native.syscall(id, &args, &mut ret, hw).map_err(|fault| match fault {
|
native.syscall(id, &args, &mut ret, hw).map_err(|fault| match fault {
|
||||||
crate::virtual_machine::VmFault::Trap(code, msg) => LogicalFrameEndingReason::Trap(TrapInfo {
|
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),
|
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 => {
|
OpCode::FrameSync => {
|
||||||
return Ok(());
|
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]
|
#[test]
|
||||||
fn test_host_return_bounded_overflow_trap() {
|
fn test_host_return_bounded_overflow_trap() {
|
||||||
let mut stack = Vec::new();
|
let mut stack = Vec::new();
|
||||||
|
|||||||
173
docs/specs/pbs/Runtime Traps.md
Normal file
173
docs/specs/pbs/Runtime Traps.md
Normal file
@ -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.
|
||||||
|
|
||||||
|
---
|
||||||
@ -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
|
## 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.
|
**Why:** Imports are compile-time, but we need an industrial linking model for multi-module PBS.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user