bQUARKz f9120e740b
dev/pbs (#8)
Co-authored-by: Nilton Constantino <nilton.constantino@visma.com>
Reviewed-on: #8
2026-03-24 13:40:22 +00:00

149 lines
6.1 KiB
Rust

//! This module defines the Application Binary Interface (ABI) of the Prometeu Virtual Machine.
//! It specifies how instructions are encoded in bytes and how they interact with memory.
use crate::opcode::OpCode;
/// Returns the size in bytes of the operands for a given OpCode.
///
/// Note: This does NOT include the 2 bytes of the OpCode itself.
/// For example, `PushI32` has a size of 4, but occupies 6 bytes in ROM (2 for OpCode + 4 for value).
pub fn operand_size(opcode: OpCode) -> usize {
match opcode {
OpCode::PushConst => 4,
OpCode::PushI32 => 4,
OpCode::PushBounded => 4,
OpCode::PushI64 => 8,
OpCode::PushF64 => 8,
OpCode::PushBool => 1,
OpCode::PopN => 4,
OpCode::Jmp | OpCode::JmpIfFalse | OpCode::JmpIfTrue => 4,
OpCode::GetGlobal | OpCode::SetGlobal => 4,
OpCode::GetLocal | OpCode::SetLocal => 4,
OpCode::Call => 4, // func_id(u32)
OpCode::Syscall => 4,
OpCode::Alloc => 8, // type_id(u32) + slots(u32)
OpCode::GateLoad | OpCode::GateStore => 4, // offset(u32)
_ => 0,
}
}
// --- HIP Trap Codes ---
/// Attempted to access a gate that does not exist or has been recycled incorrectly.
pub const TRAP_INVALID_GATE: u32 = 0x01;
/// Attempted to access a gate that has been explicitly released (RC=0).
pub const TRAP_DEAD_GATE: u32 = 0x02;
/// Attempted to access a field or index beyond the allocated slots for a gate.
pub const TRAP_OOB: u32 = 0x03;
/// Attempted a typed operation on a gate whose storage type does not match.
pub const TRAP_TYPE: u32 = 0x04;
/// The syscall ID provided is not recognized by the system.
pub const TRAP_INVALID_SYSCALL: u32 = 0x0000_0007;
/// Not enough arguments on the stack for the requested syscall.
pub const TRAP_STACK_UNDERFLOW: u32 = 0x0000_0008;
/// Attempted to access a local slot that is out of bounds for the current frame.
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;
use serde::{Deserialize, Serialize};
/// Detailed information about a source code span.
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct SourceSpan {
pub file_id: u32,
pub start: u32,
pub end: u32,
}
/// Detailed information about a runtime trap.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TrapInfo {
/// The specific trap code (e.g., TRAP_OOB).
pub code: u32,
/// The numeric value of the opcode that triggered the trap.
pub opcode: u16,
/// A human-readable message explaining the trap.
pub message: String,
/// The absolute Program Counter (PC) address where the trap occurred.
pub pc: u32,
/// Optional source span information if debug symbols are available.
pub span: Option<SourceSpan>,
}
/// Checks if an instruction is a jump (branch) instruction.
pub fn is_jump(opcode: OpCode) -> bool {
match opcode {
OpCode::Jmp | OpCode::JmpIfFalse | OpCode::JmpIfTrue => true,
_ => false,
}
}
/// Checks if an instruction has any immediate operands in the instruction stream.
pub fn has_immediate(opcode: OpCode) -> bool {
operand_size(opcode) > 0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_trap_code_stability() {
// These numeric values are normative and must not change.
assert_eq!(TRAP_INVALID_GATE, 0x01);
assert_eq!(TRAP_DEAD_GATE, 0x02);
assert_eq!(TRAP_OOB, 0x03);
assert_eq!(TRAP_TYPE, 0x04);
assert_eq!(TRAP_INVALID_SYSCALL, 0x07);
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]
fn test_abi_documentation_snapshot() {
// Snapshot of the ABI rules for traps and operands.
let abi_info = r#"
HIP Traps:
- INVALID_GATE (0x01): Non-existent gate handle.
- DEAD_GATE (0x02): Gate handle with RC=0.
- OOB (0x03): Access beyond allocated slots.
- TYPE (0x04): Type mismatch during heap access.
System Traps:
- INVALID_SYSCALL (0x07): Unknown syscall ID.
- 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)
- GateLoad: 4 bytes (u32 offset)
- GateStore: 4 bytes (u32 offset)
- PopN: 4 bytes (u32 count)
"#;
// 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- 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_FUNC, TRAP_BAD_RET_SLOTS,
operand_size(OpCode::Alloc),
operand_size(OpCode::GateLoad),
operand_size(OpCode::GateStore),
operand_size(OpCode::PopN)
);
assert_eq!(current_info.trim(), abi_info.trim());
}
}