pr 48
This commit is contained in:
parent
33908aa828
commit
be1c244b5a
@ -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",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 }));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
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
|
||||
|
||||
**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