pr 47
This commit is contained in:
parent
9d484a9636
commit
33908aa828
@ -45,6 +45,10 @@ pub const TRAP_STACK_UNDERFLOW: u32 = 0x0000_0008;
|
||||
pub const TRAP_INVALID_LOCAL: u32 = 0x0000_0009;
|
||||
/// Division or modulo by zero.
|
||||
pub const TRAP_DIV_ZERO: u32 = 0x0000_000A;
|
||||
/// Attempted to call a function that does not exist in the function table.
|
||||
pub const TRAP_INVALID_FUNC: u32 = 0x0000_000B;
|
||||
/// Executed RET with an incorrect stack height (mismatch with function metadata).
|
||||
pub const TRAP_BAD_RET_SLOTS: u32 = 0x0000_000C;
|
||||
|
||||
/// Detailed information about a runtime trap.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@ -87,6 +91,8 @@ mod tests {
|
||||
assert_eq!(TRAP_STACK_UNDERFLOW, 0x08);
|
||||
assert_eq!(TRAP_INVALID_LOCAL, 0x09);
|
||||
assert_eq!(TRAP_DIV_ZERO, 0x0A);
|
||||
assert_eq!(TRAP_INVALID_FUNC, 0x0B);
|
||||
assert_eq!(TRAP_BAD_RET_SLOTS, 0x0C);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -104,6 +110,8 @@ System Traps:
|
||||
- STACK_UNDERFLOW (0x08): Missing syscall arguments.
|
||||
- INVALID_LOCAL (0x09): Local slot out of bounds.
|
||||
- DIV_ZERO (0x0A): Division by zero.
|
||||
- INVALID_FUNC (0x0B): Function table index out of bounds.
|
||||
- BAD_RET_SLOTS (0x0C): Stack height mismatch at RET.
|
||||
|
||||
Operand Sizes:
|
||||
- Alloc: 8 bytes (u32 type_id, u32 slots)
|
||||
@ -114,9 +122,9 @@ Operand Sizes:
|
||||
// This test serves as a "doc-lock".
|
||||
// If you change the ABI, you must update this string.
|
||||
let current_info = format!(
|
||||
"\nHIP Traps:\n- INVALID_GATE (0x{:02X}): Non-existent gate handle.\n- DEAD_GATE (0x{:02X}): Gate handle with RC=0.\n- OOB (0x{:02X}): Access beyond allocated slots.\n- TYPE (0x{:02X}): Type mismatch during heap access.\n\nSystem Traps:\n- INVALID_SYSCALL (0x{:02X}): Unknown syscall ID.\n- STACK_UNDERFLOW (0x{:02X}): Missing syscall arguments.\n- INVALID_LOCAL (0x{:02X}): Local slot out of bounds.\n- DIV_ZERO (0x{:02X}): Division by zero.\n\nOperand Sizes:\n- Alloc: {} bytes (u32 type_id, u32 slots)\n- GateLoad: {} bytes (u32 offset)\n- GateStore: {} bytes (u32 offset)\n- PopN: {} bytes (u32 count)\n",
|
||||
"\nHIP Traps:\n- INVALID_GATE (0x{:02X}): Non-existent gate handle.\n- DEAD_GATE (0x{:02X}): Gate handle with RC=0.\n- OOB (0x{:02X}): Access beyond allocated slots.\n- TYPE (0x{:02X}): Type mismatch during heap access.\n\nSystem Traps:\n- INVALID_SYSCALL (0x{:02X}): Unknown syscall ID.\n- STACK_UNDERFLOW (0x{:02X}): Missing syscall arguments.\n- INVALID_LOCAL (0x{:02X}): Local slot out of bounds.\n- DIV_ZERO (0x{:02X}): Division by zero.\n- INVALID_FUNC (0x{:02X}): Function table index out of bounds.\n- BAD_RET_SLOTS (0x{:02X}): Stack height mismatch at RET.\n\nOperand Sizes:\n- Alloc: {} bytes (u32 type_id, u32 slots)\n- GateLoad: {} bytes (u32 offset)\n- GateStore: {} bytes (u32 offset)\n- PopN: {} bytes (u32 count)\n",
|
||||
TRAP_INVALID_GATE, TRAP_DEAD_GATE, TRAP_OOB, TRAP_TYPE,
|
||||
TRAP_INVALID_SYSCALL, TRAP_STACK_UNDERFLOW, TRAP_INVALID_LOCAL, TRAP_DIV_ZERO,
|
||||
TRAP_INVALID_SYSCALL, TRAP_STACK_UNDERFLOW, TRAP_INVALID_LOCAL, TRAP_DIV_ZERO, TRAP_INVALID_FUNC, TRAP_BAD_RET_SLOTS,
|
||||
operand_size(OpCode::Alloc),
|
||||
operand_size(OpCode::GateLoad),
|
||||
operand_size(OpCode::GateStore),
|
||||
|
||||
@ -686,6 +686,7 @@ mod tests {
|
||||
rom: vec![
|
||||
0x17, 0x00, // PushI32
|
||||
0x00, 0x00, 0x00, 0x00, // value 0
|
||||
0x11, 0x00, // Pop
|
||||
0x51, 0x00 // Ret
|
||||
],
|
||||
}).unwrap();
|
||||
|
||||
@ -5,7 +5,7 @@ use crate::virtual_machine::value::Value;
|
||||
use crate::virtual_machine::{NativeInterface, Program, VmInitError};
|
||||
use prometeu_bytecode::opcode::OpCode;
|
||||
use prometeu_bytecode::pbc::{self, ConstantPoolEntry};
|
||||
use prometeu_bytecode::abi::{TrapInfo, TRAP_OOB, TRAP_DIV_ZERO, TRAP_TYPE};
|
||||
use prometeu_bytecode::abi::{TrapInfo, TRAP_OOB, TRAP_DIV_ZERO, TRAP_TYPE, TRAP_INVALID_FUNC, TRAP_BAD_RET_SLOTS};
|
||||
|
||||
/// Reason why the Virtual Machine stopped execution during a specific run.
|
||||
/// This allows the system to decide if it should continue execution in the next tick
|
||||
@ -835,10 +835,20 @@ impl VirtualMachine {
|
||||
}
|
||||
OpCode::Call => {
|
||||
let func_id = u32::from_le_bytes(instr.imm[0..4].try_into().unwrap()) as usize;
|
||||
let callee = self.program.functions.get(func_id).ok_or_else(|| LogicalFrameEndingReason::Panic(format!("Invalid func_id {}", func_id)))?;
|
||||
let callee = self.program.functions.get(func_id).ok_or_else(|| {
|
||||
LogicalFrameEndingReason::Trap(TrapInfo {
|
||||
code: TRAP_INVALID_FUNC,
|
||||
opcode: opcode as u16,
|
||||
message: format!("Invalid func_id {}", func_id),
|
||||
pc: start_pc as u32,
|
||||
})
|
||||
})?;
|
||||
|
||||
if self.operand_stack.len() < callee.param_slots as usize {
|
||||
return Err(LogicalFrameEndingReason::Panic("Stack underflow during CALL: not enough arguments".into()));
|
||||
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()
|
||||
)));
|
||||
}
|
||||
|
||||
let stack_base = self.operand_stack.len() - callee.param_slots as usize;
|
||||
@ -860,7 +870,22 @@ impl VirtualMachine {
|
||||
let func = &self.program.functions[frame.func_idx];
|
||||
let return_slots = func.return_slots as usize;
|
||||
|
||||
// Copy return values
|
||||
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;
|
||||
|
||||
if current_height != expected_height {
|
||||
return Err(LogicalFrameEndingReason::Trap(TrapInfo {
|
||||
code: TRAP_BAD_RET_SLOTS,
|
||||
opcode: opcode as u16,
|
||||
message: format!(
|
||||
"Incorrect stack height at RET in func {}: expected {} slots (stack_base={} + params={} + locals={} + returns={}), got {}",
|
||||
frame.func_idx, expected_height, frame.stack_base, func.param_slots, func.local_slots, return_slots, current_height
|
||||
),
|
||||
pc: start_pc as u32,
|
||||
}));
|
||||
}
|
||||
|
||||
// Copy return values (preserving order: pop return_slots values, then reverse to push back)
|
||||
let mut return_vals = Vec::with_capacity(return_slots);
|
||||
for _ in 0..return_slots {
|
||||
return_vals.push(self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?);
|
||||
@ -1355,8 +1380,10 @@ mod tests {
|
||||
let res = vm.step(&mut native, &mut hw); // RET -> should fail
|
||||
assert!(res.is_err());
|
||||
match res.unwrap_err() {
|
||||
LogicalFrameEndingReason::Panic(msg) => assert!(msg.contains("Stack underflow")),
|
||||
_ => panic!("Expected Panic"),
|
||||
LogicalFrameEndingReason::Trap(trap) => {
|
||||
assert_eq!(trap.code, TRAP_BAD_RET_SLOTS);
|
||||
}
|
||||
_ => panic!("Expected Trap(TRAP_BAD_RET_SLOTS)"),
|
||||
}
|
||||
|
||||
// Agora com valor de retorno
|
||||
@ -1475,6 +1502,7 @@ mod tests {
|
||||
rom.extend_from_slice(&(OpCode::PushScope as u16).to_le_bytes());
|
||||
rom.extend_from_slice(&(OpCode::PushI64 as u16).to_le_bytes());
|
||||
rom.extend_from_slice(&300i64.to_le_bytes());
|
||||
rom.extend_from_slice(&(OpCode::PopScope as u16).to_le_bytes());
|
||||
rom.extend_from_slice(&(OpCode::Ret as u16).to_le_bytes());
|
||||
|
||||
let functions = vec![
|
||||
@ -1505,7 +1533,7 @@ mod tests {
|
||||
assert!(vm.halted);
|
||||
assert_eq!(vm.operand_stack.len(), 2);
|
||||
assert_eq!(vm.operand_stack[0], Value::Int64(100));
|
||||
assert_eq!(vm.operand_stack[1], Value::Int64(300));
|
||||
assert_eq!(vm.operand_stack[1], Value::Int64(200));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1747,6 +1775,7 @@ mod tests {
|
||||
let rom = vec![
|
||||
0x17, 0x00, // PushI32
|
||||
0x00, 0x00, 0x00, 0x00, // value 0
|
||||
0x11, 0x00, // Pop
|
||||
0x51, 0x00 // Ret
|
||||
];
|
||||
let mut vm = VirtualMachine::new(rom, vec![]);
|
||||
@ -2027,6 +2056,240 @@ mod tests {
|
||||
assert!(res.is_ok(), "Should NOT fail if Call pattern is in immediate: {:?}", res);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calling_convention_add() {
|
||||
let mut native = MockNative;
|
||||
let mut hw = MockHardware;
|
||||
|
||||
// F0 (entry):
|
||||
// PUSH_I32 10
|
||||
// PUSH_I32 20
|
||||
// CALL 1 (add)
|
||||
// HALT
|
||||
// F1 (add):
|
||||
// GET_LOCAL 0 (a)
|
||||
// GET_LOCAL 1 (b)
|
||||
// ADD
|
||||
// RET (1 slot)
|
||||
|
||||
let mut rom = Vec::new();
|
||||
// F0
|
||||
let f0_start = 0;
|
||||
rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes());
|
||||
rom.extend_from_slice(&10i32.to_le_bytes());
|
||||
rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes());
|
||||
rom.extend_from_slice(&20i32.to_le_bytes());
|
||||
rom.extend_from_slice(&(OpCode::Call as u16).to_le_bytes());
|
||||
rom.extend_from_slice(&1u32.to_le_bytes());
|
||||
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
|
||||
let f0_len = rom.len() - f0_start;
|
||||
|
||||
// F1
|
||||
let f1_start = rom.len() as u32;
|
||||
rom.extend_from_slice(&(OpCode::GetLocal as u16).to_le_bytes());
|
||||
rom.extend_from_slice(&0u32.to_le_bytes());
|
||||
rom.extend_from_slice(&(OpCode::GetLocal as u16).to_le_bytes());
|
||||
rom.extend_from_slice(&1u32.to_le_bytes());
|
||||
rom.extend_from_slice(&(OpCode::Add as u16).to_le_bytes());
|
||||
rom.extend_from_slice(&(OpCode::Ret as u16).to_le_bytes());
|
||||
let f1_len = rom.len() as u32 - f1_start;
|
||||
|
||||
let mut vm = VirtualMachine::new(rom, vec![]);
|
||||
vm.program.functions = std::sync::Arc::from(vec![
|
||||
FunctionMeta {
|
||||
code_offset: f0_start as u32,
|
||||
code_len: f0_len as u32,
|
||||
..Default::default()
|
||||
},
|
||||
FunctionMeta {
|
||||
code_offset: f1_start,
|
||||
code_len: f1_len,
|
||||
param_slots: 2,
|
||||
return_slots: 1,
|
||||
..Default::default()
|
||||
},
|
||||
]);
|
||||
|
||||
vm.prepare_call("0");
|
||||
let report = vm.run_budget(100, &mut native, &mut hw).unwrap();
|
||||
assert_eq!(report.reason, LogicalFrameEndingReason::Halted);
|
||||
assert_eq!(vm.operand_stack.last().unwrap(), &Value::Int32(30));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calling_convention_multi_slot_return() {
|
||||
let mut native = MockNative;
|
||||
let mut hw = MockHardware;
|
||||
|
||||
// F0:
|
||||
// CALL 1
|
||||
// HALT
|
||||
// F1:
|
||||
// PUSH_I32 100
|
||||
// PUSH_I32 200
|
||||
// RET (2 slots)
|
||||
|
||||
let mut rom = Vec::new();
|
||||
// F0
|
||||
let f0_start = 0;
|
||||
rom.extend_from_slice(&(OpCode::Call as u16).to_le_bytes());
|
||||
rom.extend_from_slice(&1u32.to_le_bytes());
|
||||
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
|
||||
let f0_len = rom.len() - f0_start;
|
||||
|
||||
// F1
|
||||
let f1_start = rom.len() as u32;
|
||||
rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes());
|
||||
rom.extend_from_slice(&100i32.to_le_bytes());
|
||||
rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes());
|
||||
rom.extend_from_slice(&200i32.to_le_bytes());
|
||||
rom.extend_from_slice(&(OpCode::Ret as u16).to_le_bytes());
|
||||
let f1_len = rom.len() as u32 - f1_start;
|
||||
|
||||
let mut vm = VirtualMachine::new(rom, vec![]);
|
||||
vm.program.functions = std::sync::Arc::from(vec![
|
||||
FunctionMeta {
|
||||
code_offset: f0_start as u32,
|
||||
code_len: f0_len as u32,
|
||||
..Default::default()
|
||||
},
|
||||
FunctionMeta {
|
||||
code_offset: f1_start,
|
||||
code_len: f1_len,
|
||||
param_slots: 0,
|
||||
return_slots: 2,
|
||||
..Default::default()
|
||||
},
|
||||
]);
|
||||
|
||||
vm.prepare_call("0");
|
||||
let report = vm.run_budget(100, &mut native, &mut hw).unwrap();
|
||||
assert_eq!(report.reason, LogicalFrameEndingReason::Halted);
|
||||
// Stack should be [100, 200]
|
||||
assert_eq!(vm.operand_stack.len(), 2);
|
||||
assert_eq!(vm.operand_stack[0], Value::Int32(100));
|
||||
assert_eq!(vm.operand_stack[1], Value::Int32(200));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calling_convention_void_call() {
|
||||
let mut native = MockNative;
|
||||
let mut hw = MockHardware;
|
||||
|
||||
// F0:
|
||||
// PUSH_I32 42
|
||||
// CALL 1
|
||||
// HALT
|
||||
// F1:
|
||||
// POP
|
||||
// RET (0 slots)
|
||||
|
||||
let mut rom = Vec::new();
|
||||
let f0_start = 0;
|
||||
rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes());
|
||||
rom.extend_from_slice(&42i32.to_le_bytes());
|
||||
rom.extend_from_slice(&(OpCode::Call as u16).to_le_bytes());
|
||||
rom.extend_from_slice(&1u32.to_le_bytes());
|
||||
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
|
||||
let f0_len = rom.len() - f0_start;
|
||||
|
||||
let f1_start = rom.len() as u32;
|
||||
rom.extend_from_slice(&(OpCode::Ret as u16).to_le_bytes());
|
||||
let f1_len = rom.len() as u32 - f1_start;
|
||||
|
||||
let mut vm = VirtualMachine::new(rom, vec![]);
|
||||
vm.program.functions = std::sync::Arc::from(vec![
|
||||
FunctionMeta {
|
||||
code_offset: f0_start as u32,
|
||||
code_len: f0_len as u32,
|
||||
..Default::default()
|
||||
},
|
||||
FunctionMeta {
|
||||
code_offset: f1_start,
|
||||
code_len: f1_len,
|
||||
param_slots: 1,
|
||||
return_slots: 0,
|
||||
..Default::default()
|
||||
},
|
||||
]);
|
||||
|
||||
vm.prepare_call("0");
|
||||
let report = vm.run_budget(100, &mut native, &mut hw).unwrap();
|
||||
assert_eq!(report.reason, LogicalFrameEndingReason::Halted);
|
||||
assert_eq!(vm.operand_stack.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trap_invalid_func() {
|
||||
let mut native = MockNative;
|
||||
let mut hw = MockHardware;
|
||||
|
||||
// CALL 99 (invalid)
|
||||
let mut rom = Vec::new();
|
||||
rom.extend_from_slice(&(OpCode::Call as u16).to_le_bytes());
|
||||
rom.extend_from_slice(&99u32.to_le_bytes());
|
||||
|
||||
let mut vm = VirtualMachine::new(rom, vec![]);
|
||||
let report = vm.run_budget(100, &mut native, &mut hw).unwrap();
|
||||
|
||||
match report.reason {
|
||||
LogicalFrameEndingReason::Trap(trap) => {
|
||||
assert_eq!(trap.code, TRAP_INVALID_FUNC);
|
||||
assert_eq!(trap.opcode, OpCode::Call as u16);
|
||||
}
|
||||
_ => panic!("Expected Trap(TRAP_INVALID_FUNC), got {:?}", report.reason),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trap_bad_ret_slots() {
|
||||
let mut native = MockNative;
|
||||
let mut hw = MockHardware;
|
||||
|
||||
// F0: CALL 1; HALT
|
||||
// F1: PUSH_I32 42; RET (expected 0 slots)
|
||||
|
||||
let mut rom = Vec::new();
|
||||
let f0_start = 0;
|
||||
rom.extend_from_slice(&(OpCode::Call as u16).to_le_bytes());
|
||||
rom.extend_from_slice(&1u32.to_le_bytes());
|
||||
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
|
||||
let f0_len = rom.len() - f0_start;
|
||||
|
||||
let f1_start = rom.len() as u32;
|
||||
rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes());
|
||||
rom.extend_from_slice(&42i32.to_le_bytes());
|
||||
rom.extend_from_slice(&(OpCode::Ret as u16).to_le_bytes());
|
||||
let f1_len = rom.len() as u32 - f1_start;
|
||||
|
||||
let mut vm = VirtualMachine::new(rom, vec![]);
|
||||
vm.program.functions = std::sync::Arc::from(vec![
|
||||
FunctionMeta {
|
||||
code_offset: f0_start as u32,
|
||||
code_len: f0_len as u32,
|
||||
..Default::default()
|
||||
},
|
||||
FunctionMeta {
|
||||
code_offset: f1_start,
|
||||
code_len: f1_len,
|
||||
param_slots: 0,
|
||||
return_slots: 0, // ERROR: function pushes 42 but returns 0
|
||||
..Default::default()
|
||||
},
|
||||
]);
|
||||
|
||||
vm.prepare_call("0");
|
||||
let report = vm.run_budget(100, &mut native, &mut hw).unwrap();
|
||||
|
||||
match report.reason {
|
||||
LogicalFrameEndingReason::Trap(trap) => {
|
||||
assert_eq!(trap.code, TRAP_BAD_RET_SLOTS);
|
||||
assert_eq!(trap.opcode, OpCode::Ret as u16);
|
||||
assert!(trap.message.contains("Incorrect stack height"));
|
||||
}
|
||||
_ => panic!("Expected Trap(TRAP_BAD_RET_SLOTS), got {:?}", report.reason),
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn test_locals_round_trip() {
|
||||
let mut native = MockNative;
|
||||
@ -2044,6 +2307,7 @@ mod tests {
|
||||
rom.extend_from_slice(&0u32.to_le_bytes());
|
||||
rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes());
|
||||
rom.extend_from_slice(&0i32.to_le_bytes());
|
||||
rom.extend_from_slice(&(OpCode::Pop as u16).to_le_bytes());
|
||||
rom.extend_from_slice(&(OpCode::GetLocal as u16).to_le_bytes());
|
||||
rom.extend_from_slice(&0u32.to_le_bytes());
|
||||
rom.extend_from_slice(&(OpCode::Ret as u16).to_le_bytes());
|
||||
@ -2051,7 +2315,7 @@ mod tests {
|
||||
let mut vm = VirtualMachine::new(rom, vec![]);
|
||||
vm.program.functions = std::sync::Arc::from(vec![FunctionMeta {
|
||||
code_offset: 0,
|
||||
code_len: 18,
|
||||
code_len: 20,
|
||||
local_slots: 1,
|
||||
return_slots: 1,
|
||||
..Default::default()
|
||||
|
||||
@ -1,77 +1,3 @@
|
||||
## PR-07 — Calling convention v0: CALL / RET / multi-slot returns
|
||||
|
||||
**Why:** Without a correct call model, PBS isn’t executable.
|
||||
|
||||
### Scope
|
||||
|
||||
* Introduce `CALL <u16 func_id>`
|
||||
|
||||
* caller pushes args (slots)
|
||||
* callee frame allocates locals
|
||||
* Introduce `RET`
|
||||
|
||||
* callee must leave exactly `return_slots` on operand stack at `RET`
|
||||
* VM pops frame and transfers return slots to caller
|
||||
* Define return mechanics for `void` (`return_slots=0`)
|
||||
|
||||
### Deliverables
|
||||
|
||||
* `FunctionTable` indexing and bounds checks
|
||||
* Deterministic traps:
|
||||
|
||||
* `TRAP_INVALID_FUNC`
|
||||
* `TRAP_BAD_RET_SLOTS`
|
||||
|
||||
### Tests
|
||||
|
||||
* `fn add(a:int,b:int):int { return a+b; }`
|
||||
* multi-slot return (e.g., `Pad` flattened)
|
||||
* void call
|
||||
|
||||
### Acceptance
|
||||
|
||||
* Calls are stable and stack-clean.
|
||||
|
||||
---
|
||||
|
||||
## PR-08 — Host syscalls v0: stable ABI, multi-slot args/returns
|
||||
|
||||
**Why:** PBS relies on deterministic syscalls; ABI must be frozen and enforced.
|
||||
|
||||
### Scope
|
||||
|
||||
* Unify syscall invocation opcode:
|
||||
|
||||
* `SYSCALL <u16 id> <u8 arg_slots> <u8 ret_slots>`
|
||||
* Runtime validates:
|
||||
|
||||
* pops `arg_slots`
|
||||
* pushes `ret_slots`
|
||||
* Implement/confirm:
|
||||
|
||||
* `GfxClear565 (0x1010)`
|
||||
* `InputPadSnapshot (0x2010)`
|
||||
* `InputTouchSnapshot (0x2011)`
|
||||
|
||||
### Deliverables
|
||||
|
||||
* A `SyscallRegistry` mapping id -> handler + signature
|
||||
* Deterministic traps:
|
||||
|
||||
* `TRAP_INVALID_SYSCALL`
|
||||
* `TRAP_SYSCALL_SIG_MISMATCH`
|
||||
|
||||
### Tests
|
||||
|
||||
* syscall isolated tests
|
||||
* wrong signature traps
|
||||
|
||||
### Acceptance
|
||||
|
||||
* Syscalls are “industrial”: typed by signature, deterministic, no host surprises.
|
||||
|
||||
---
|
||||
|
||||
## PR-09 — Debug info v0: spans, symbols, and traceable traps
|
||||
|
||||
**Why:** Industrial debugging requires actionable failures.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user