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;
|
pub const TRAP_INVALID_LOCAL: u32 = 0x0000_0009;
|
||||||
/// Division or modulo by zero.
|
/// Division or modulo by zero.
|
||||||
pub const TRAP_DIV_ZERO: u32 = 0x0000_000A;
|
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.
|
/// Detailed information about a runtime trap.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
@ -87,6 +91,8 @@ mod tests {
|
|||||||
assert_eq!(TRAP_STACK_UNDERFLOW, 0x08);
|
assert_eq!(TRAP_STACK_UNDERFLOW, 0x08);
|
||||||
assert_eq!(TRAP_INVALID_LOCAL, 0x09);
|
assert_eq!(TRAP_INVALID_LOCAL, 0x09);
|
||||||
assert_eq!(TRAP_DIV_ZERO, 0x0A);
|
assert_eq!(TRAP_DIV_ZERO, 0x0A);
|
||||||
|
assert_eq!(TRAP_INVALID_FUNC, 0x0B);
|
||||||
|
assert_eq!(TRAP_BAD_RET_SLOTS, 0x0C);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -104,6 +110,8 @@ System Traps:
|
|||||||
- STACK_UNDERFLOW (0x08): Missing syscall arguments.
|
- STACK_UNDERFLOW (0x08): Missing syscall arguments.
|
||||||
- INVALID_LOCAL (0x09): Local slot out of bounds.
|
- INVALID_LOCAL (0x09): Local slot out of bounds.
|
||||||
- DIV_ZERO (0x0A): Division by zero.
|
- 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:
|
Operand Sizes:
|
||||||
- Alloc: 8 bytes (u32 type_id, u32 slots)
|
- Alloc: 8 bytes (u32 type_id, u32 slots)
|
||||||
@ -114,9 +122,9 @@ Operand Sizes:
|
|||||||
// This test serves as a "doc-lock".
|
// This test serves as a "doc-lock".
|
||||||
// If you change the ABI, you must update this string.
|
// If you change the ABI, you must update this string.
|
||||||
let current_info = format!(
|
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_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::Alloc),
|
||||||
operand_size(OpCode::GateLoad),
|
operand_size(OpCode::GateLoad),
|
||||||
operand_size(OpCode::GateStore),
|
operand_size(OpCode::GateStore),
|
||||||
|
|||||||
@ -686,6 +686,7 @@ mod tests {
|
|||||||
rom: vec![
|
rom: vec![
|
||||||
0x17, 0x00, // PushI32
|
0x17, 0x00, // PushI32
|
||||||
0x00, 0x00, 0x00, 0x00, // value 0
|
0x00, 0x00, 0x00, 0x00, // value 0
|
||||||
|
0x11, 0x00, // Pop
|
||||||
0x51, 0x00 // Ret
|
0x51, 0x00 // Ret
|
||||||
],
|
],
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
|
|||||||
@ -5,7 +5,7 @@ use crate::virtual_machine::value::Value;
|
|||||||
use crate::virtual_machine::{NativeInterface, Program, VmInitError};
|
use crate::virtual_machine::{NativeInterface, Program, VmInitError};
|
||||||
use prometeu_bytecode::opcode::OpCode;
|
use prometeu_bytecode::opcode::OpCode;
|
||||||
use prometeu_bytecode::pbc::{self, ConstantPoolEntry};
|
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.
|
/// 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
|
/// This allows the system to decide if it should continue execution in the next tick
|
||||||
@ -835,10 +835,20 @@ impl VirtualMachine {
|
|||||||
}
|
}
|
||||||
OpCode::Call => {
|
OpCode::Call => {
|
||||||
let func_id = u32::from_le_bytes(instr.imm[0..4].try_into().unwrap()) as usize;
|
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 {
|
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;
|
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 func = &self.program.functions[frame.func_idx];
|
||||||
let return_slots = func.return_slots as usize;
|
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);
|
let mut return_vals = Vec::with_capacity(return_slots);
|
||||||
for _ in 0..return_slots {
|
for _ in 0..return_slots {
|
||||||
return_vals.push(self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?);
|
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
|
let res = vm.step(&mut native, &mut hw); // RET -> should fail
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
match res.unwrap_err() {
|
match res.unwrap_err() {
|
||||||
LogicalFrameEndingReason::Panic(msg) => assert!(msg.contains("Stack underflow")),
|
LogicalFrameEndingReason::Trap(trap) => {
|
||||||
_ => panic!("Expected Panic"),
|
assert_eq!(trap.code, TRAP_BAD_RET_SLOTS);
|
||||||
|
}
|
||||||
|
_ => panic!("Expected Trap(TRAP_BAD_RET_SLOTS)"),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Agora com valor de retorno
|
// 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::PushScope as u16).to_le_bytes());
|
||||||
rom.extend_from_slice(&(OpCode::PushI64 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(&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());
|
rom.extend_from_slice(&(OpCode::Ret as u16).to_le_bytes());
|
||||||
|
|
||||||
let functions = vec![
|
let functions = vec![
|
||||||
@ -1505,7 +1533,7 @@ mod tests {
|
|||||||
assert!(vm.halted);
|
assert!(vm.halted);
|
||||||
assert_eq!(vm.operand_stack.len(), 2);
|
assert_eq!(vm.operand_stack.len(), 2);
|
||||||
assert_eq!(vm.operand_stack[0], Value::Int64(100));
|
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]
|
#[test]
|
||||||
@ -1747,6 +1775,7 @@ mod tests {
|
|||||||
let rom = vec![
|
let rom = vec![
|
||||||
0x17, 0x00, // PushI32
|
0x17, 0x00, // PushI32
|
||||||
0x00, 0x00, 0x00, 0x00, // value 0
|
0x00, 0x00, 0x00, 0x00, // value 0
|
||||||
|
0x11, 0x00, // Pop
|
||||||
0x51, 0x00 // Ret
|
0x51, 0x00 // Ret
|
||||||
];
|
];
|
||||||
let mut vm = VirtualMachine::new(rom, vec![]);
|
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);
|
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]
|
#[test]
|
||||||
fn test_locals_round_trip() {
|
fn test_locals_round_trip() {
|
||||||
let mut native = MockNative;
|
let mut native = MockNative;
|
||||||
@ -2044,6 +2307,7 @@ mod tests {
|
|||||||
rom.extend_from_slice(&0u32.to_le_bytes());
|
rom.extend_from_slice(&0u32.to_le_bytes());
|
||||||
rom.extend_from_slice(&(OpCode::PushI32 as u16).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(&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(&(OpCode::GetLocal as u16).to_le_bytes());
|
||||||
rom.extend_from_slice(&0u32.to_le_bytes());
|
rom.extend_from_slice(&0u32.to_le_bytes());
|
||||||
rom.extend_from_slice(&(OpCode::Ret as u16).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![]);
|
let mut vm = VirtualMachine::new(rom, vec![]);
|
||||||
vm.program.functions = std::sync::Arc::from(vec![FunctionMeta {
|
vm.program.functions = std::sync::Arc::from(vec![FunctionMeta {
|
||||||
code_offset: 0,
|
code_offset: 0,
|
||||||
code_len: 18,
|
code_len: 20,
|
||||||
local_slots: 1,
|
local_slots: 1,
|
||||||
return_slots: 1,
|
return_slots: 1,
|
||||||
..Default::default()
|
..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
|
## PR-09 — Debug info v0: spans, symbols, and traceable traps
|
||||||
|
|
||||||
**Why:** Industrial debugging requires actionable failures.
|
**Why:** Industrial debugging requires actionable failures.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user