Co-authored-by: Nilton Constantino <nilton.constantino@visma.com> Reviewed-on: #8
149 lines
6.1 KiB
Rust
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());
|
|
}
|
|
}
|