bytecode extension

This commit is contained in:
Nilton Constantino 2026-01-20 09:26:12 +00:00
parent a1fe7cbca9
commit 6b033f9aa6
No known key found for this signature in database
6 changed files with 398 additions and 65 deletions

View File

@ -99,7 +99,7 @@ mod tests {
fn test_get_state_serialization() {
let resp = DebugResponse::GetState {
pc: 42,
stack_top: vec![Value::Integer(10), Value::String("test".into()), Value::Boolean(true)],
stack_top: vec![Value::Int64(10), Value::String("test".into()), Value::Boolean(true)],
frame_index: 5,
app_id: 1,
};

View File

@ -471,7 +471,7 @@ mod tests {
os.current_app_id = 123;
// 1. Normal log test
vm.push(Value::Integer(2)); // Info
vm.push(Value::Int64(2)); // Info
vm.push(Value::String("Hello Log".to_string()));
let res = os.syscall(0x5001, &mut vm, &mut hw);
assert!(res.is_ok());
@ -483,7 +483,7 @@ mod tests {
// 2. Truncation test
let long_msg = "A".repeat(300);
vm.push(Value::Integer(3)); // Warn
vm.push(Value::Int64(3)); // Warn
vm.push(Value::String(long_msg));
os.syscall(0x5001, &mut vm, &mut hw).unwrap();
@ -494,13 +494,13 @@ mod tests {
// 3. Rate Limit Test
// We already made 2 logs. The limit is 10.
for i in 0..8 {
vm.push(Value::Integer(2));
vm.push(Value::Int64(2));
vm.push(Value::String(format!("Log {}", i)));
os.syscall(0x5001, &mut vm, &mut hw).unwrap();
}
// The 11th log should be ignored (and generate a system warning)
vm.push(Value::Integer(2));
vm.push(Value::Int64(2));
vm.push(Value::String("Eleventh log".to_string()));
os.syscall(0x5001, &mut vm, &mut hw).unwrap();
@ -513,7 +513,7 @@ mod tests {
// 4. Rate limit reset test in the next frame
os.begin_logical_frame(&InputSignals::default(), &mut hw);
vm.push(Value::Integer(2));
vm.push(Value::Int64(2));
vm.push(Value::String("New frame log".to_string()));
os.syscall(0x5001, &mut vm, &mut hw).unwrap();
@ -521,8 +521,8 @@ mod tests {
assert_eq!(recent[0].msg, "New frame log");
// 5. LOG_WRITE_TAG test
vm.push(Value::Integer(2)); // Info
vm.push(Value::Integer(42)); // Tag
vm.push(Value::Int64(2)); // Info
vm.push(Value::Int64(42)); // Tag
vm.push(Value::String("Tagged Log".to_string()));
os.syscall(0x5002, &mut vm, &mut hw).unwrap();
@ -670,13 +670,13 @@ impl NativeInterface for PrometeuOS {
_ => return Err("Expected string path".into()),
};
if self.fs_state != FsState::Mounted {
vm.push(Value::Integer(-1));
vm.push(Value::Int64(-1));
return Ok(100);
}
let handle = self.next_handle;
self.open_files.insert(handle, path);
self.next_handle += 1;
vm.push(Value::Integer(handle as i64));
vm.push(Value::Int64(handle as i64));
Ok(200)
}
// FS_READ(handle) -> content

View File

@ -7,6 +7,7 @@ pub enum OpCode {
Halt = 0x01,
Jmp = 0x02,
JmpIfFalse = 0x03,
JmpIfTrue = 0x04,
// 6.2 Stack
PushConst = 0x10,
@ -16,6 +17,7 @@ pub enum OpCode {
PushI64 = 0x14,
PushF64 = 0x15,
PushBool = 0x16,
PushI32 = 0x17,
// 6.3 Arithmetic
Add = 0x20,
@ -31,6 +33,14 @@ pub enum OpCode {
And = 0x34,
Or = 0x35,
Not = 0x36,
BitAnd = 0x37,
BitOr = 0x38,
BitXor = 0x39,
Shl = 0x3A,
Shr = 0x3B,
Lte = 0x3C,
Gte = 0x3D,
Neg = 0x3E,
// 6.5 Variables
GetGlobal = 0x40,
@ -63,6 +73,7 @@ impl TryFrom<u16> for OpCode {
0x01 => Ok(OpCode::Halt),
0x02 => Ok(OpCode::Jmp),
0x03 => Ok(OpCode::JmpIfFalse),
0x04 => Ok(OpCode::JmpIfTrue),
0x10 => Ok(OpCode::PushConst),
0x11 => Ok(OpCode::Pop),
0x12 => Ok(OpCode::Dup),
@ -70,6 +81,7 @@ impl TryFrom<u16> for OpCode {
0x14 => Ok(OpCode::PushI64),
0x15 => Ok(OpCode::PushF64),
0x16 => Ok(OpCode::PushBool),
0x17 => Ok(OpCode::PushI32),
0x20 => Ok(OpCode::Add),
0x21 => Ok(OpCode::Sub),
0x22 => Ok(OpCode::Mul),
@ -81,6 +93,14 @@ impl TryFrom<u16> for OpCode {
0x34 => Ok(OpCode::And),
0x35 => Ok(OpCode::Or),
0x36 => Ok(OpCode::Not),
0x37 => Ok(OpCode::BitAnd),
0x38 => Ok(OpCode::BitOr),
0x39 => Ok(OpCode::BitXor),
0x3A => Ok(OpCode::Shl),
0x3B => Ok(OpCode::Shr),
0x3C => Ok(OpCode::Lte),
0x3D => Ok(OpCode::Gte),
0x3E => Ok(OpCode::Neg),
0x40 => Ok(OpCode::GetGlobal),
0x41 => Ok(OpCode::SetGlobal),
0x42 => Ok(OpCode::GetLocal),
@ -106,6 +126,7 @@ impl OpCode {
OpCode::Halt => 1,
OpCode::Jmp => 2,
OpCode::JmpIfFalse => 3,
OpCode::JmpIfTrue => 3,
OpCode::PushConst => 2,
OpCode::Pop => 1,
OpCode::Dup => 1,
@ -113,6 +134,7 @@ impl OpCode {
OpCode::PushI64 => 2,
OpCode::PushF64 => 2,
OpCode::PushBool => 2,
OpCode::PushI32 => 2,
OpCode::Add => 2,
OpCode::Sub => 2,
OpCode::Mul => 4,
@ -124,6 +146,14 @@ impl OpCode {
OpCode::And => 2,
OpCode::Or => 2,
OpCode::Not => 1,
OpCode::BitAnd => 2,
OpCode::BitOr => 2,
OpCode::BitXor => 2,
OpCode::Shl => 2,
OpCode::Shr => 2,
OpCode::Lte => 2,
OpCode::Gte => 2,
OpCode::Neg => 1,
OpCode::GetGlobal => 3,
OpCode::SetGlobal => 3,
OpCode::GetLocal => 2,

View File

@ -1,9 +1,11 @@
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Value {
Integer(i64),
Int32(i32),
Int64(i64),
Float(f64),
Boolean(bool),
String(String),
@ -14,10 +16,15 @@ pub enum Value {
impl PartialEq for Value {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Value::Integer(a), Value::Integer(b)) => a == b,
(Value::Int32(a), Value::Int32(b)) => a == b,
(Value::Int64(a), Value::Int64(b)) => a == b,
(Value::Int32(a), Value::Int64(b)) => *a as i64 == *b,
(Value::Int64(a), Value::Int32(b)) => *a == *b as i64,
(Value::Float(a), Value::Float(b)) => a == b,
(Value::Integer(a), Value::Float(b)) => *a as f64 == *b,
(Value::Float(a), Value::Integer(b)) => *a == *b as f64,
(Value::Int32(a), Value::Float(b)) => *a as f64 == *b,
(Value::Float(a), Value::Int32(b)) => *a == *b as f64,
(Value::Int64(a), Value::Float(b)) => *a as f64 == *b,
(Value::Float(a), Value::Int64(b)) => *a == *b as f64,
(Value::Boolean(a), Value::Boolean(b)) => a == b,
(Value::String(a), Value::String(b)) => a == b,
(Value::Ref(a), Value::Ref(b)) => a == b,
@ -27,10 +34,30 @@ impl PartialEq for Value {
}
}
impl PartialOrd for Value {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (self, other) {
(Value::Int32(a), Value::Int32(b)) => a.partial_cmp(b),
(Value::Int64(a), Value::Int64(b)) => a.partial_cmp(b),
(Value::Int32(a), Value::Int64(b)) => (*a as i64).partial_cmp(b),
(Value::Int64(a), Value::Int32(b)) => a.partial_cmp(&(*b as i64)),
(Value::Float(a), Value::Float(b)) => a.partial_cmp(b),
(Value::Int32(a), Value::Float(b)) => (*a as f64).partial_cmp(b),
(Value::Float(a), Value::Int32(b)) => a.partial_cmp(&(*b as f64)),
(Value::Int64(a), Value::Float(b)) => (*a as f64).partial_cmp(b),
(Value::Float(a), Value::Int64(b)) => a.partial_cmp(&(*b as f64)),
(Value::Boolean(a), Value::Boolean(b)) => a.partial_cmp(b),
(Value::String(a), Value::String(b)) => a.partial_cmp(b),
_ => None,
}
}
}
impl Value {
pub fn as_float(&self) -> Option<f64> {
match self {
Value::Integer(i) => Some(*i as f64),
Value::Int32(i) => Some(*i as f64),
Value::Int64(i) => Some(*i as f64),
Value::Float(f) => Some(*f),
_ => None,
}
@ -38,7 +65,8 @@ impl Value {
pub fn as_integer(&self) -> Option<i64> {
match self {
Value::Integer(i) => Some(*i),
Value::Int32(i) => Some(*i as i64),
Value::Int64(i) => Some(*i),
Value::Float(f) => Some(*f as i64),
_ => None,
}
@ -51,12 +79,19 @@ mod tests {
#[test]
fn test_value_equality() {
assert_eq!(Value::Integer(10), Value::Integer(10));
assert_eq!(Value::Int32(10), Value::Int32(10));
assert_eq!(Value::Int64(10), Value::Int64(10));
assert_eq!(Value::Int32(10), Value::Int64(10));
assert_eq!(Value::Int64(10), Value::Int32(10));
assert_eq!(Value::Float(10.5), Value::Float(10.5));
assert_eq!(Value::Integer(10), Value::Float(10.0));
assert_eq!(Value::Float(10.0), Value::Integer(10));
assert_ne!(Value::Integer(10), Value::Integer(11));
assert_ne!(Value::Integer(10), Value::Float(10.1));
assert_eq!(Value::Int32(10), Value::Float(10.0));
assert_eq!(Value::Float(10.0), Value::Int32(10));
assert_eq!(Value::Int64(10), Value::Float(10.0));
assert_eq!(Value::Float(10.0), Value::Int64(10));
assert_ne!(Value::Int32(10), Value::Int32(11));
assert_ne!(Value::Int64(10), Value::Int64(11));
assert_ne!(Value::Int32(10), Value::Int64(11));
assert_ne!(Value::Int32(10), Value::Float(10.1));
assert_eq!(Value::Boolean(true), Value::Boolean(true));
assert_ne!(Value::Boolean(true), Value::Boolean(false));
assert_eq!(Value::String("oi".into()), Value::String("oi".into()));
@ -65,9 +100,13 @@ mod tests {
#[test]
fn test_value_conversions() {
let v_int = Value::Integer(42);
assert_eq!(v_int.as_float(), Some(42.0));
assert_eq!(v_int.as_integer(), Some(42));
let v_int32 = Value::Int32(42);
assert_eq!(v_int32.as_float(), Some(42.0));
assert_eq!(v_int32.as_integer(), Some(42));
let v_int64 = Value::Int64(42);
assert_eq!(v_int64.as_float(), Some(42.0));
assert_eq!(v_int64.as_integer(), Some(42));
let v_float = Value::Float(42.7);
assert_eq!(v_float.as_float(), Some(42.7));

View File

@ -127,9 +127,9 @@ impl VirtualMachine {
cursor += 1;
match tag {
1 => { // Integer (64-bit)
1 => { // Int64 (64-bit)
let val = self.read_i64_at(bytes, &mut cursor)?;
cp.push(Value::Integer(val));
cp.push(Value::Int64(val));
}
2 => { // Float (64-bit)
let val = self.read_f64_at(bytes, &mut cursor)?;
@ -148,6 +148,10 @@ impl VirtualMachine {
cursor += len;
cp.push(Value::String(s));
}
5 => { // Int32 (32-bit)
let val = self.read_u32_at(bytes, &mut cursor)? as i32;
cp.push(Value::Int32(val));
}
_ => cp.push(Value::Null),
}
}
@ -312,6 +316,13 @@ impl VirtualMachine {
self.pc = addr;
}
}
OpCode::JmpIfTrue => {
let addr = self.read_u32()? as usize;
let val = self.pop()?;
if let Value::Boolean(true) = val {
self.pc = addr;
}
}
OpCode::PushConst => {
let idx = self.read_u32()? as usize;
let val = self.program.constant_pool.get(idx).cloned().ok_or("Invalid constant index")?;
@ -319,7 +330,11 @@ impl VirtualMachine {
}
OpCode::PushI64 => {
let val = self.read_i64()?;
self.push(Value::Integer(val));
self.push(Value::Int64(val));
}
OpCode::PushI32 => {
let val = self.read_i32()?;
self.push(Value::Int32(val));
}
OpCode::PushF64 => {
let val = self.read_f64()?;
@ -343,40 +358,75 @@ impl VirtualMachine {
self.push(b);
}
OpCode::Add => self.binary_op(|a, b| match (a, b) {
(Value::Integer(a), Value::Integer(b)) => Ok(Value::Integer(a.wrapping_add(b))),
(Value::Int32(a), Value::Int32(b)) => Ok(Value::Int32(a.wrapping_add(b))),
(Value::Int64(a), Value::Int64(b)) => Ok(Value::Int64(a.wrapping_add(b))),
(Value::Int32(a), Value::Int64(b)) => Ok(Value::Int64((a as i64).wrapping_add(b))),
(Value::Int64(a), Value::Int32(b)) => Ok(Value::Int64(a.wrapping_add(b as i64))),
(Value::Float(a), Value::Float(b)) => Ok(Value::Float(a + b)),
(Value::Integer(a), Value::Float(b)) => Ok(Value::Float(a as f64 + b)),
(Value::Float(a), Value::Integer(b)) => Ok(Value::Float(a + b as f64)),
(Value::Int32(a), Value::Float(b)) => Ok(Value::Float(a as f64 + b)),
(Value::Float(a), Value::Int32(b)) => Ok(Value::Float(a + b as f64)),
(Value::Int64(a), Value::Float(b)) => Ok(Value::Float(a as f64 + b)),
(Value::Float(a), Value::Int64(b)) => Ok(Value::Float(a + b as f64)),
_ => Err("Invalid types for ADD".into()),
})?,
OpCode::Sub => self.binary_op(|a, b| match (a, b) {
(Value::Integer(a), Value::Integer(b)) => Ok(Value::Integer(a.wrapping_sub(b))),
(Value::Int32(a), Value::Int32(b)) => Ok(Value::Int32(a.wrapping_sub(b))),
(Value::Int64(a), Value::Int64(b)) => Ok(Value::Int64(a.wrapping_sub(b))),
(Value::Int32(a), Value::Int64(b)) => Ok(Value::Int64((a as i64).wrapping_sub(b))),
(Value::Int64(a), Value::Int32(b)) => Ok(Value::Int64(a.wrapping_sub(b as i64))),
(Value::Float(a), Value::Float(b)) => Ok(Value::Float(a - b)),
(Value::Integer(a), Value::Float(b)) => Ok(Value::Float(a as f64 - b)),
(Value::Float(a), Value::Integer(b)) => Ok(Value::Float(a - b as f64)),
(Value::Int32(a), Value::Float(b)) => Ok(Value::Float(a as f64 - b)),
(Value::Float(a), Value::Int32(b)) => Ok(Value::Float(a - b as f64)),
(Value::Int64(a), Value::Float(b)) => Ok(Value::Float(a as f64 - b)),
(Value::Float(a), Value::Int64(b)) => Ok(Value::Float(a - b as f64)),
_ => Err("Invalid types for SUB".into()),
})?,
OpCode::Mul => self.binary_op(|a, b| match (a, b) {
(Value::Integer(a), Value::Integer(b)) => Ok(Value::Integer(a.wrapping_mul(b))),
(Value::Int32(a), Value::Int32(b)) => Ok(Value::Int32(a.wrapping_mul(b))),
(Value::Int64(a), Value::Int64(b)) => Ok(Value::Int64(a.wrapping_mul(b))),
(Value::Int32(a), Value::Int64(b)) => Ok(Value::Int64((a as i64).wrapping_mul(b))),
(Value::Int64(a), Value::Int32(b)) => Ok(Value::Int64(a.wrapping_mul(b as i64))),
(Value::Float(a), Value::Float(b)) => Ok(Value::Float(a * b)),
(Value::Integer(a), Value::Float(b)) => Ok(Value::Float(a as f64 * b)),
(Value::Float(a), Value::Integer(b)) => Ok(Value::Float(a * b as f64)),
(Value::Int32(a), Value::Float(b)) => Ok(Value::Float(a as f64 * b)),
(Value::Float(a), Value::Int32(b)) => Ok(Value::Float(a * b as f64)),
(Value::Int64(a), Value::Float(b)) => Ok(Value::Float(a as f64 * b)),
(Value::Float(a), Value::Int64(b)) => Ok(Value::Float(a * b as f64)),
_ => Err("Invalid types for MUL".into()),
})?,
OpCode::Div => self.binary_op(|a, b| match (a, b) {
(Value::Integer(a), Value::Integer(b)) => {
(Value::Int32(a), Value::Int32(b)) => {
if b == 0 { return Err("Division by zero".into()); }
Ok(Value::Integer(a / b))
Ok(Value::Int32(a / b))
}
(Value::Int64(a), Value::Int64(b)) => {
if b == 0 { return Err("Division by zero".into()); }
Ok(Value::Int64(a / b))
}
(Value::Int32(a), Value::Int64(b)) => {
if b == 0 { return Err("Division by zero".into()); }
Ok(Value::Int64(a as i64 / b))
}
(Value::Int64(a), Value::Int32(b)) => {
if b == 0 { return Err("Division by zero".into()); }
Ok(Value::Int64(a / b as i64))
}
(Value::Float(a), Value::Float(b)) => {
if b == 0.0 { return Err("Division by zero".into()); }
Ok(Value::Float(a / b))
}
(Value::Integer(a), Value::Float(b)) => {
(Value::Int32(a), Value::Float(b)) => {
if b == 0.0 { return Err("Division by zero".into()); }
Ok(Value::Float(a as f64 / b))
}
(Value::Float(a), Value::Integer(b)) => {
(Value::Float(a), Value::Int32(b)) => {
if b == 0 { return Err("Division by zero".into()); }
Ok(Value::Float(a / b as f64))
}
(Value::Int64(a), Value::Float(b)) => {
if b == 0.0 { return Err("Division by zero".into()); }
Ok(Value::Float(a as f64 / b))
}
(Value::Float(a), Value::Int64(b)) => {
if b == 0 { return Err("Division by zero".into()); }
Ok(Value::Float(a / b as f64))
}
@ -385,22 +435,24 @@ impl VirtualMachine {
OpCode::Eq => self.binary_op(|a, b| Ok(Value::Boolean(a == b)))?,
OpCode::Neq => self.binary_op(|a, b| Ok(Value::Boolean(a != b)))?,
OpCode::Lt => self.binary_op(|a, b| {
match (a, b) {
(Value::Integer(a), Value::Integer(b)) => Ok(Value::Boolean(a < b)),
(Value::Float(a), Value::Float(b)) => Ok(Value::Boolean(a < b)),
(Value::Integer(a), Value::Float(b)) => Ok(Value::Boolean((a as f64) < b)),
(Value::Float(a), Value::Integer(b)) => Ok(Value::Boolean(a < (b as f64))),
_ => Err("Invalid types for LT".into()),
}
a.partial_cmp(&b)
.map(|o| Value::Boolean(o == std::cmp::Ordering::Less))
.ok_or_else(|| "Invalid types for LT".into())
})?,
OpCode::Gt => self.binary_op(|a, b| {
match (a, b) {
(Value::Integer(a), Value::Integer(b)) => Ok(Value::Boolean(a > b)),
(Value::Float(a), Value::Float(b)) => Ok(Value::Boolean(a > b)),
(Value::Integer(a), Value::Float(b)) => Ok(Value::Boolean((a as f64) > b)),
(Value::Float(a), Value::Integer(b)) => Ok(Value::Boolean(a > (b as f64))),
_ => Err("Invalid types for GT".into()),
}
a.partial_cmp(&b)
.map(|o| Value::Boolean(o == std::cmp::Ordering::Greater))
.ok_or_else(|| "Invalid types for GT".into())
})?,
OpCode::Lte => self.binary_op(|a, b| {
a.partial_cmp(&b)
.map(|o| Value::Boolean(o != std::cmp::Ordering::Greater))
.ok_or_else(|| "Invalid types for LTE".into())
})?,
OpCode::Gte => self.binary_op(|a, b| {
a.partial_cmp(&b)
.map(|o| Value::Boolean(o != std::cmp::Ordering::Less))
.ok_or_else(|| "Invalid types for GTE".into())
})?,
OpCode::And => self.binary_op(|a, b| match (a, b) {
(Value::Boolean(a), Value::Boolean(b)) => Ok(Value::Boolean(a && b)),
@ -418,6 +470,50 @@ impl VirtualMachine {
return Err("Invalid type for NOT".into());
}
}
OpCode::BitAnd => self.binary_op(|a, b| match (a, b) {
(Value::Int32(a), Value::Int32(b)) => Ok(Value::Int32(a & b)),
(Value::Int64(a), Value::Int64(b)) => Ok(Value::Int64(a & b)),
(Value::Int32(a), Value::Int64(b)) => Ok(Value::Int64((a as i64) & b)),
(Value::Int64(a), Value::Int32(b)) => Ok(Value::Int64(a & (b as i64))),
_ => Err("Invalid types for BitAnd".into()),
})?,
OpCode::BitOr => self.binary_op(|a, b| match (a, b) {
(Value::Int32(a), Value::Int32(b)) => Ok(Value::Int32(a | b)),
(Value::Int64(a), Value::Int64(b)) => Ok(Value::Int64(a | b)),
(Value::Int32(a), Value::Int64(b)) => Ok(Value::Int64((a as i64) | b)),
(Value::Int64(a), Value::Int32(b)) => Ok(Value::Int64(a | (b as i64))),
_ => Err("Invalid types for BitOr".into()),
})?,
OpCode::BitXor => self.binary_op(|a, b| match (a, b) {
(Value::Int32(a), Value::Int32(b)) => Ok(Value::Int32(a ^ b)),
(Value::Int64(a), Value::Int64(b)) => Ok(Value::Int64(a ^ b)),
(Value::Int32(a), Value::Int64(b)) => Ok(Value::Int64((a as i64) ^ b)),
(Value::Int64(a), Value::Int32(b)) => Ok(Value::Int64(a ^ (b as i64))),
_ => Err("Invalid types for BitXor".into()),
})?,
OpCode::Shl => self.binary_op(|a, b| match (a, b) {
(Value::Int32(a), Value::Int32(b)) => Ok(Value::Int32(a.wrapping_shl(b as u32))),
(Value::Int64(a), Value::Int64(b)) => Ok(Value::Int64(a.wrapping_shl(b as u32))),
(Value::Int32(a), Value::Int64(b)) => Ok(Value::Int64((a as i64).wrapping_shl(b as u32))),
(Value::Int64(a), Value::Int32(b)) => Ok(Value::Int64(a.wrapping_shl(b as u32))),
_ => Err("Invalid types for Shl".into()),
})?,
OpCode::Shr => self.binary_op(|a, b| match (a, b) {
(Value::Int32(a), Value::Int32(b)) => Ok(Value::Int32(a.wrapping_shr(b as u32))),
(Value::Int64(a), Value::Int64(b)) => Ok(Value::Int64(a.wrapping_shr(b as u32))),
(Value::Int32(a), Value::Int64(b)) => Ok(Value::Int64((a as i64).wrapping_shr(b as u32))),
(Value::Int64(a), Value::Int32(b)) => Ok(Value::Int64(a.wrapping_shr(b as u32))),
_ => Err("Invalid types for Shr".into()),
})?,
OpCode::Neg => {
let val = self.pop()?;
match val {
Value::Int32(a) => self.push(Value::Int32(a.wrapping_neg())),
Value::Int64(a) => self.push(Value::Int64(a.wrapping_neg())),
Value::Float(a) => self.push(Value::Float(-a)),
_ => return Err("Invalid type for Neg".into()),
}
}
OpCode::GetGlobal => {
let idx = self.read_u32()? as usize;
let val = self.globals.get(idx).cloned().ok_or("Invalid global index")?;
@ -548,6 +644,20 @@ impl VirtualMachine {
Ok(u32::from_le_bytes(bytes))
}
fn read_i32(&mut self) -> Result<i32, String> {
if self.pc + 4 > self.program.rom.len() {
return Err("Unexpected end of ROM".into());
}
let bytes = [
self.program.rom[self.pc],
self.program.rom[self.pc + 1],
self.program.rom[self.pc + 2],
self.program.rom[self.pc + 3],
];
self.pc += 4;
Ok(i32::from_le_bytes(bytes))
}
fn read_i64(&mut self) -> Result<i64, String> {
if self.pc + 8 > self.program.rom.len() {
return Err("Unexpected end of ROM".into());
@ -661,7 +771,7 @@ mod tests {
let mut hw = MockHardware;
vm.step(&mut native, &mut hw).unwrap();
assert_eq!(vm.peek().unwrap(), &Value::Integer(42));
assert_eq!(vm.peek().unwrap(), &Value::Int64(42));
}
#[test]
@ -814,7 +924,7 @@ mod tests {
vm2.step(&mut native, &mut hw).unwrap(); // RET
assert_eq!(vm2.operand_stack.len(), 1);
assert_eq!(vm2.pop().unwrap(), Value::Integer(123));
assert_eq!(vm2.pop().unwrap(), Value::Int64(123));
}
#[test]
@ -866,16 +976,12 @@ mod tests {
vm.step(&mut native, &mut hw).unwrap(); // PopScope 2
assert_eq!(vm.scope_stack.len(), 1);
assert_eq!(vm.operand_stack.len(), 2);
assert_eq!(vm.operand_stack.last().unwrap(), &Value::Integer(2));
assert_eq!(vm.operand_stack.last().unwrap(), &Value::Int64(2));
vm.step(&mut native, &mut hw).unwrap(); // PopScope 1
assert_eq!(vm.scope_stack.len(), 0);
assert_eq!(vm.operand_stack.len(), 1);
assert_eq!(vm.operand_stack.last().unwrap(), &Value::Integer(1));
}
fn hw_to_mut(hw: &mut MockHardware) -> &mut dyn HardwareBridge {
hw
assert_eq!(vm.operand_stack.last().unwrap(), &Value::Int64(1));
}
#[test]
@ -929,12 +1035,149 @@ mod tests {
// Then it pushes return value (300).
// So the stack should have [100, 300].
assert_eq!(vm.operand_stack.len(), 2);
assert_eq!(vm.operand_stack[0], Value::Integer(100));
assert_eq!(vm.operand_stack[1], Value::Integer(300));
assert_eq!(vm.operand_stack[0], Value::Int64(100));
assert_eq!(vm.operand_stack[1], Value::Int64(300));
// Check if scope_stack was leaked (it currently would be if we don't clear it on RET)
// The PR doesn't explicitly say RET should clear scope_stack, but it's good practice.
// "Não mexe em scopes intermediários (eles devem já ter sido fechados)"
// If they were closed, scope_stack would be empty for this frame.
}
#[test]
fn test_push_i32() {
let mut rom = Vec::new();
rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes());
rom.extend_from_slice(&42i32.to_le_bytes());
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
let mut native = MockNative;
let mut hw = MockHardware;
vm.step(&mut native, &mut hw).unwrap();
assert_eq!(vm.peek().unwrap(), &Value::Int32(42));
}
#[test]
fn test_bitwise_promotion() {
let mut native = MockNative;
let mut hw = MockHardware;
// i32 & i32 -> i32
let mut rom = Vec::new();
rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes());
rom.extend_from_slice(&0xF0i32.to_le_bytes());
rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes());
rom.extend_from_slice(&0x0Fi32.to_le_bytes());
rom.extend_from_slice(&(OpCode::BitAnd as u16).to_le_bytes());
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
vm.step(&mut native, &mut hw).unwrap();
vm.step(&mut native, &mut hw).unwrap();
vm.step(&mut native, &mut hw).unwrap();
assert_eq!(vm.pop().unwrap(), Value::Int32(0));
// i32 | i64 -> i64
let mut rom = Vec::new();
rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes());
rom.extend_from_slice(&0xF0i32.to_le_bytes());
rom.extend_from_slice(&(OpCode::PushI64 as u16).to_le_bytes());
rom.extend_from_slice(&0x0Fi64.to_le_bytes());
rom.extend_from_slice(&(OpCode::BitOr as u16).to_le_bytes());
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
vm.step(&mut native, &mut hw).unwrap();
vm.step(&mut native, &mut hw).unwrap();
vm.step(&mut native, &mut hw).unwrap();
assert_eq!(vm.pop().unwrap(), Value::Int64(0xFF));
}
#[test]
fn test_comparisons_lte_gte() {
let mut native = MockNative;
let mut hw = MockHardware;
// 10 <= 20 (true)
let mut rom = Vec::new();
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::Lte as u16).to_le_bytes());
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
vm.step(&mut native, &mut hw).unwrap();
vm.step(&mut native, &mut hw).unwrap();
vm.step(&mut native, &mut hw).unwrap();
assert_eq!(vm.pop().unwrap(), Value::Boolean(true));
// 20 >= 20 (true)
let mut rom = Vec::new();
rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes());
rom.extend_from_slice(&20i32.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::Gte as u16).to_le_bytes());
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
vm.step(&mut native, &mut hw).unwrap();
vm.step(&mut native, &mut hw).unwrap();
vm.step(&mut native, &mut hw).unwrap();
assert_eq!(vm.pop().unwrap(), Value::Boolean(true));
}
#[test]
fn test_negation() {
let mut native = MockNative;
let mut hw = MockHardware;
let mut rom = Vec::new();
rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes());
rom.extend_from_slice(&42i32.to_le_bytes());
rom.extend_from_slice(&(OpCode::Neg as u16).to_le_bytes());
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
vm.step(&mut native, &mut hw).unwrap();
vm.step(&mut native, &mut hw).unwrap();
assert_eq!(vm.pop().unwrap(), Value::Int32(-42));
}
#[test]
fn test_jmp_if_true() {
let mut native = MockNative;
let mut hw = MockHardware;
// Corrected Calculations:
// 0-1: PushBool
// 2: 1 (u8)
// 3-4: JmpIfTrue
// 5-8: addr (u32)
// 9-10: Halt (Offset 9)
// 11-12: PushI32 (Offset 11)
// 13-16: 100 (i32)
// 17-18: Halt
let mut rom = Vec::new();
rom.extend_from_slice(&(OpCode::PushBool as u16).to_le_bytes());
rom.push(1);
rom.extend_from_slice(&(OpCode::JmpIfTrue as u16).to_le_bytes());
rom.extend_from_slice(&(11u32).to_le_bytes());
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); // Offset 9
rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes()); // Offset 11
rom.extend_from_slice(&100i32.to_le_bytes());
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
vm.step(&mut native, &mut hw).unwrap(); // PushBool
vm.step(&mut native, &mut hw).unwrap(); // JmpIfTrue
assert_eq!(vm.pc, 11);
vm.step(&mut native, &mut hw).unwrap(); // PushI32
assert_eq!(vm.pop().unwrap(), Value::Int32(100));
}
}

View File

@ -61,13 +61,21 @@ Properties:
| Type | Description |
| --------- | ------------------------- |
| `integer` | 64-bit signed integer |
| `int32` | 32-bit signed integer |
| `int64` | 64-bit signed integer |
| `float` | 64-bit floating point |
| `boolean` | true/false |
| `string` | immutable UTF-8 |
| `null` | absence of value |
| `ref` | heap reference |
### 3.1 Numeric Promotion
The VM promotes types automatically during operations:
* `int32` + `int32``int32`
* `int32` + `int64``int64`
* `int` + `float``float`
* Bitwise operations promote `int32` to `int64` if any operand is `int64`.
Do not exist:
* magic coercions
@ -123,6 +131,7 @@ State:
| `HALT` | 1 | Terminates execution |
| `JMP addr` | 2 | Unconditional jump |
| `JMP_IF_FALSE addr` | 3 | Jumps if top is false |
| `JMP_IF_TRUE addr` | 3 | Jumps if top is true |
---
@ -134,6 +143,10 @@ State:
| `POP` | 1 | Removes top |
| `DUP` | 1 | Duplicates top |
| `SWAP` | 1 | Swaps two tops |
| `PUSH_I32 v` | 2 | Pushes 32-bit int |
| `PUSH_I64 v` | 2 | Pushes 64-bit int |
| `PUSH_F64 v` | 2 | Pushes 64-bit float |
| `PUSH_BOOL v` | 2 | Pushes boolean |
---
@ -156,9 +169,17 @@ State:
| `NEQ` | 2 |
| `LT` | 2 |
| `GT` | 2 |
| `LTE` | 2 |
| `GTE` | 2 |
| `AND` | 2 |
| `OR` | 2 |
| `NOT` | 1 |
| `BIT_AND` | 2 |
| `BIT_OR` | 2 |
| `BIT_XOR` | 2 |
| `SHL` | 2 |
| `SHR` | 2 |
| `NEG` | 1 |
---