#[cfg(not(feature = "std"))]
use alloc::{format, vec, vec::Vec};
use crate::bytecode::{Instruction, InstructionStream, Program, Register};
use crate::opcodes;
use crate::error::Error;
use crate::model::{Domain, XqmxModel, XqmxSample};
use crate::tracer::{NoopTracer, StepState, Tracer};
use crate::value::RegVal;
#[derive(Debug)]
pub(crate) enum LoopKind {
Range { current: i64, end: i64 },
Iter {
elements: IterElements,
start_offset: usize,
index: usize,
},
}
#[derive(Debug)]
pub(crate) enum IterElements {
Int(Vec<i64>),
Xqmx(Vec<XqmxModel>),
}
impl IterElements {
fn len(&self) -> usize {
match self {
Self::Int(v) => v.len(),
Self::Xqmx(v) => v.len(),
}
}
}
#[derive(Debug)]
pub(crate) struct LoopFrame {
pub kind: LoopKind,
pub body_start: usize,
}
#[derive(Debug)]
pub(crate) enum StepResult {
Continue,
Jump(u16),
Seek(usize),
Halt,
StartLoop { kind: LoopKind },
}
fn resolve_iter_slice(
pos: usize,
start: i64,
end: i64,
len: usize,
) -> Result<(usize, core::ops::Range<usize>), Error> {
let start_us = usize::try_from(start).map_err(|_| Error::IndexOutOfBounds {
pos,
index: start,
len,
})?;
let end_us = usize::try_from(end).map_err(|_| Error::IndexOutOfBounds {
pos,
index: end,
len,
})?;
if start_us > len {
return Err(Error::IndexOutOfBounds {
pos,
index: start,
len,
});
}
if end_us > len {
return Err(Error::IndexOutOfBounds {
pos,
index: end,
len,
});
}
if start_us > end_us {
return Err(Error::IndexOutOfBounds {
pos,
index: end,
len,
});
}
Ok((start_us, start_us..end_us))
}
fn sign_extend_be(bytes: &[u8]) -> i64 {
debug_assert!(!bytes.is_empty() && bytes.len() <= 8);
let mut v = 0i64;
for &b in bytes {
v = (v << 8) | i64::from(b);
}
let n = u32::try_from(bytes.len().min(8)).unwrap_or(8);
let shift = 64u32 - n * 8;
(v << shift) >> shift
}
const DEFAULT_STEP_LIMIT: u64 = 10_000_000;
#[derive(Debug)]
pub struct Vm {
stack: Vec<i64>,
regs: Vec<RegVal>,
loop_stack: Vec<LoopFrame>,
calldata: Vec<RegVal>,
outputs: Vec<RegVal>,
step_limit: u64,
steps: u64,
}
impl Default for Vm {
fn default() -> Self {
Self::new()
}
}
impl Vm {
pub fn new() -> Self {
Self {
stack: Vec::new(),
regs: {
let mut v = Vec::with_capacity(256);
v.resize_with(256, || RegVal::Unset);
v
},
loop_stack: Vec::new(),
calldata: Vec::new(),
outputs: Vec::new(),
step_limit: DEFAULT_STEP_LIMIT,
steps: 0,
}
}
pub fn set_calldata(&mut self, data: Vec<RegVal>) -> &mut Self {
self.calldata = data;
self
}
pub fn set_output_slots(&mut self, n: usize) -> &mut Self {
self.outputs = vec![RegVal::Unset; n];
self
}
pub fn set_step_limit(&mut self, limit: u64) -> &mut Self {
self.step_limit = if limit == 0 { u64::MAX } else { limit };
self
}
pub fn stack(&self) -> &[i64] {
&self.stack
}
pub fn outputs(&self) -> &[RegVal] {
&self.outputs
}
pub fn register(&self, r: u8) -> &RegVal {
self.regs
.get(usize::from(r))
.unwrap_or_else(|| unreachable!("register slot {} always valid in a 256-slot file", r))
}
pub fn set_register(&mut self, r: u8, val: RegVal) -> &mut Self {
*self.regs.get_mut(usize::from(r)).unwrap_or_else(|| {
unreachable!("register slot {} always valid in a 256-slot file", r)
}) = val;
self
}
pub fn steps(&self) -> u64 {
self.steps
}
pub fn reset(&mut self) {
self.stack.clear();
self.regs.iter_mut().for_each(|r| *r = RegVal::Unset);
self.loop_stack.clear();
self.steps = 0;
}
pub fn run(&mut self, program: &Program) -> Result<(), Error> {
self.run_trace(&mut NoopTracer, program)
}
pub fn run_trace<T: Tracer>(&mut self, tracer: &mut T, program: &Program) -> Result<(), Error>
where
T::Error: core::fmt::Display,
{
let mut stream = InstructionStream::from_program(program);
let table = program.jump_table();
self.steps = 0;
loop {
if self.steps >= self.step_limit {
return Err(Error::StepLimitExceeded {
limit: self.step_limit,
});
}
self.steps += 1;
let Some(item) = stream.next_instruction() else {
break;
};
let (pos, _label, instr) = item.map_err(Error::from)?;
let result = if T::ENABLED {
let read_slots = instr.read_registers();
let read_regs: Vec<(u8, RegVal)> = read_slots
.as_slice()
.iter()
.filter_map(|&i| Some((i, self.regs.get(usize::from(i))?.clone())))
.collect();
let write_slots = instr.written_registers();
let pre_write: Vec<RegVal> = write_slots
.as_slice()
.iter()
.filter_map(|&i| self.regs.get(usize::from(i)).cloned())
.collect();
let result = self.dispatch(pos, instr)?;
let written_regs: Vec<(u8, RegVal)> = write_slots
.as_slice()
.iter()
.zip(pre_write.iter())
.filter_map(|(&i, pre)| {
let cur = self.regs.get(usize::from(i))?;
(cur != pre).then(|| (i, cur.clone()))
})
.collect();
let state = StepState {
pos,
step: self.steps,
instruction: &instr,
stack: &self.stack,
read_regs: &read_regs,
written_regs: &written_regs,
loop_depth: self.loop_stack.len(),
};
tracer.on_step(&state).map_err(|e| Error::TraceFailed {
pos,
message: format!("{e}"),
})?;
result
} else {
self.dispatch(pos, instr)?
};
match result {
StepResult::Continue => {}
StepResult::Halt => break,
StepResult::Jump(label) => {
let target = table.get(label).ok_or(Error::InvalidLabel { pos, label })?;
stream.seek(target).map_err(Error::from)?;
}
StepResult::Seek(target) => {
stream.seek(target).map_err(Error::from)?;
}
StepResult::StartLoop { kind } => {
let body_start = stream.pos();
self.loop_stack.push(LoopFrame { kind, body_start });
}
}
}
Ok(())
}
}
macro_rules! impl_dispatch {
(
$( ($code:literal, $variant:ident, $mnem:literal, $doc:literal,
{$($field:ident: $ftype:ty),*}) ),*
$(,)?
) => {
impl Vm {
fn dispatch(
&mut self,
pos: usize,
instr: Instruction,
) -> Result<StepResult, Error> {
match instr {
$(
Instruction::$variant { $($field),* } => {
::pastey::paste! {
self.[<exec_ $variant:snake>](pos $(, $field)*)
}
}
)*
}
}
}
};
}
opcodes!(impl_dispatch);
impl Vm {
fn pop(&mut self, pos: usize) -> Result<i64, Error> {
self.stack.pop().ok_or(Error::StackUnderflow { pos })
}
const STACK_LIMIT: usize = 8192;
fn push_stack(&mut self, v: i64, pos: usize) -> Result<(), Error> {
if self.stack.len() >= Self::STACK_LIMIT {
return Err(Error::StackOverflow { pos });
}
self.stack.push(v);
Ok(())
}
fn reg(&self, r: Register) -> &RegVal {
self.regs.get(usize::from(r.slot())).unwrap_or_else(|| {
unreachable!("register slot {} always valid in a 256-slot file", r.slot())
})
}
fn reg_mut(&mut self, r: Register) -> &mut RegVal {
self.regs.get_mut(usize::from(r.slot())).unwrap_or_else(|| {
unreachable!("register slot {} always valid in a 256-slot file", r.slot())
})
}
#[expect(
clippy::unused_self,
clippy::unnecessary_wraps,
reason = "dispatch macro requires &mut self and Result<StepResult, Error> for all exec methods"
)]
fn exec_nop(&mut self, _pos: usize) -> Result<StepResult, Error> {
Ok(StepResult::Continue)
}
#[expect(
clippy::unused_self,
clippy::unnecessary_wraps,
reason = "dispatch macro requires &mut self and Result<StepResult, Error> for all exec methods"
)]
fn exec_target(&mut self, _pos: usize) -> Result<StepResult, Error> {
Ok(StepResult::Continue)
}
#[expect(
clippy::unused_self,
clippy::unnecessary_wraps,
reason = "dispatch macro requires &mut self and Result<StepResult, Error> for all exec methods"
)]
fn exec_jump1(&mut self, _pos: usize, label: u8) -> Result<StepResult, Error> {
Ok(StepResult::Jump(u16::from(label)))
}
#[expect(
clippy::unused_self,
clippy::unnecessary_wraps,
reason = "dispatch macro requires &mut self and Result<StepResult, Error> for all exec methods"
)]
fn exec_jump2(&mut self, _pos: usize, label: u16) -> Result<StepResult, Error> {
Ok(StepResult::Jump(label))
}
fn exec_jump_i1(&mut self, pos: usize, label: u8) -> Result<StepResult, Error> {
let cond = self.pop(pos)?;
if cond != 0 {
Ok(StepResult::Jump(u16::from(label)))
} else {
Ok(StepResult::Continue)
}
}
fn exec_jump_i2(&mut self, pos: usize, label: u16) -> Result<StepResult, Error> {
let cond = self.pop(pos)?;
if cond != 0 {
Ok(StepResult::Jump(label))
} else {
Ok(StepResult::Continue)
}
}
fn exec_next(&mut self, pos: usize) -> Result<StepResult, Error> {
let (should_loop, body_start) = {
let frame = self
.loop_stack
.last_mut()
.ok_or(Error::NoActiveLoop { pos })?;
match &mut frame.kind {
LoopKind::Range { current, end } => {
*current += 1;
let looping = *current < *end;
(looping, frame.body_start)
}
LoopKind::Iter {
elements, index, ..
} => {
*index += 1;
let looping = *index < elements.len();
(looping, frame.body_start)
}
}
};
if should_loop {
Ok(StepResult::Seek(body_start))
} else {
let _ = self.loop_stack.pop();
Ok(StepResult::Continue)
}
}
fn exec_lidx(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
let frame = self.loop_stack.last().ok_or(Error::NoActiveLoop { pos })?;
let value = match &frame.kind {
LoopKind::Range { current, .. } => *current,
LoopKind::Iter {
start_offset,
index,
..
} => i64::try_from(start_offset.saturating_add(*index)).unwrap_or(i64::MAX),
};
*self
.regs
.get_mut(usize::from(reg.slot()))
.unwrap_or_else(|| unreachable!("register slot always valid")) = RegVal::Int(value);
Ok(StepResult::Continue)
}
fn exec_l_val(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
let frame = self.loop_stack.last().ok_or(Error::NoActiveLoop { pos })?;
match &frame.kind {
LoopKind::Range { current, .. } => {
*self
.regs
.get_mut(usize::from(reg.slot()))
.unwrap_or_else(|| unreachable!("register slot always valid")) =
RegVal::Int(*current);
}
LoopKind::Iter {
elements, index, ..
} => {
let idx = *index;
let val = match elements {
IterElements::Int(v) => {
RegVal::Int(*v.get(idx).ok_or(Error::IndexOutOfBounds {
pos,
index: i64::try_from(idx).unwrap_or(i64::MAX),
len: v.len(),
})?)
}
IterElements::Xqmx(v) => RegVal::Model(
v.get(idx)
.ok_or(Error::IndexOutOfBounds {
pos,
index: i64::try_from(idx).unwrap_or(i64::MAX),
len: v.len(),
})?
.clone(),
),
};
*self
.regs
.get_mut(usize::from(reg.slot()))
.unwrap_or_else(|| unreachable!("register slot always valid")) = val;
}
}
Ok(StepResult::Continue)
}
fn exec_range(&mut self, pos: usize) -> Result<StepResult, Error> {
let count = self.pop(pos)?;
let start = self.pop(pos)?;
Ok(StepResult::StartLoop {
kind: LoopKind::Range {
current: start,
end: start.wrapping_add(count),
},
})
}
fn exec_iter(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
let end = self.pop(pos)?;
let start = self.pop(pos)?;
match self.reg(reg) {
RegVal::VecInt(v) => {
let len = v.len();
let (start_offset, range) = resolve_iter_slice(pos, start, end, len)?;
let copy = v
.get(range)
.unwrap_or_else(|| unreachable!("resolve_iter_slice already validated"))
.to_vec();
Ok(StepResult::StartLoop {
kind: LoopKind::Iter {
elements: IterElements::Int(copy),
start_offset,
index: 0,
},
})
}
RegVal::VecXqmx(v) => {
let len = v.len();
let (start_offset, range) = resolve_iter_slice(pos, start, end, len)?;
let copy = v
.get(range)
.unwrap_or_else(|| unreachable!("resolve_iter_slice already validated"))
.to_vec();
Ok(StepResult::StartLoop {
kind: LoopKind::Iter {
elements: IterElements::Xqmx(copy),
start_offset,
index: 0,
},
})
}
other => Err(Error::RegisterType {
reg: reg.slot(),
expected: "vec",
got: other.type_name(),
}),
}
}
#[expect(
clippy::unused_self,
clippy::unnecessary_wraps,
reason = "dispatch macro requires &mut self and Result<StepResult, Error> for all exec methods"
)]
fn exec_halt(&mut self, _pos: usize) -> Result<StepResult, Error> {
Ok(StepResult::Halt)
}
fn exec_push1(&mut self, pos: usize, val: [u8; 1]) -> Result<StepResult, Error> {
self.push_stack(sign_extend_be(&val), pos)?;
Ok(StepResult::Continue)
}
fn exec_push2(&mut self, pos: usize, val: [u8; 2]) -> Result<StepResult, Error> {
self.push_stack(sign_extend_be(&val), pos)?;
Ok(StepResult::Continue)
}
fn exec_push3(&mut self, pos: usize, val: [u8; 3]) -> Result<StepResult, Error> {
self.push_stack(sign_extend_be(&val), pos)?;
Ok(StepResult::Continue)
}
fn exec_push4(&mut self, pos: usize, val: [u8; 4]) -> Result<StepResult, Error> {
self.push_stack(sign_extend_be(&val), pos)?;
Ok(StepResult::Continue)
}
fn exec_push5(&mut self, pos: usize, val: [u8; 5]) -> Result<StepResult, Error> {
self.push_stack(sign_extend_be(&val), pos)?;
Ok(StepResult::Continue)
}
fn exec_push6(&mut self, pos: usize, val: [u8; 6]) -> Result<StepResult, Error> {
self.push_stack(sign_extend_be(&val), pos)?;
Ok(StepResult::Continue)
}
fn exec_push7(&mut self, pos: usize, val: [u8; 7]) -> Result<StepResult, Error> {
self.push_stack(sign_extend_be(&val), pos)?;
Ok(StepResult::Continue)
}
fn exec_push8(&mut self, pos: usize, val: [u8; 8]) -> Result<StepResult, Error> {
self.push_stack(sign_extend_be(&val), pos)?;
Ok(StepResult::Continue)
}
fn exec_pop(&mut self, pos: usize) -> Result<StepResult, Error> {
let _ = self.pop(pos)?;
Ok(StepResult::Continue)
}
fn exec_copy(&mut self, pos: usize) -> Result<StepResult, Error> {
let top = *self.stack.last().ok_or(Error::StackUnderflow { pos })?;
self.push_stack(top, pos)?;
Ok(StepResult::Continue)
}
fn exec_swap(&mut self, pos: usize) -> Result<StepResult, Error> {
let len = self.stack.len();
if len < 2 {
return Err(Error::StackUnderflow { pos });
}
self.stack.swap(len - 1, len - 2);
Ok(StepResult::Continue)
}
fn exec_load(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
if matches!(self.reg(reg), RegVal::Unset) {
return Err(Error::UnsetRegister {
pos,
reg: reg.slot(),
});
}
let v = self.reg(reg).as_int().map_err(|got| Error::RegisterType {
reg: reg.slot(),
expected: "int",
got,
})?;
self.push_stack(v, pos)?;
Ok(StepResult::Continue)
}
fn exec_stow(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
let v = self.pop(pos)?;
*self.reg_mut(reg) = RegVal::Int(v);
Ok(StepResult::Continue)
}
fn exec_input(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
let idx = self.pop(pos)?;
let usize_idx = usize::try_from(idx)
.ok()
.filter(|&i| i < self.calldata.len())
.ok_or(Error::CallDataIndex {
index: idx,
len: self.calldata.len(),
})?;
let val = self
.calldata
.get(usize_idx)
.unwrap_or_else(|| unreachable!("usize_idx < calldata.len() checked above"))
.clone();
*self.reg_mut(reg) = val;
Ok(StepResult::Continue)
}
#[expect(
clippy::unnecessary_wraps,
reason = "dispatch macro requires Result<StepResult, Error> for all exec methods"
)]
fn exec_drop(&mut self, _pos: usize, reg: Register) -> Result<StepResult, Error> {
*self.reg_mut(reg) = RegVal::Unset;
Ok(StepResult::Continue)
}
#[expect(
clippy::unnecessary_wraps,
reason = "dispatch macro requires Result<StepResult, Error> for all exec methods"
)]
fn exec_sclr(&mut self, _pos: usize) -> Result<StepResult, Error> {
self.stack.clear();
Ok(StepResult::Continue)
}
fn exec_output(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
let idx = self.pop(pos)?;
let usize_idx = usize::try_from(idx)
.ok()
.filter(|&i| i < self.outputs.len())
.ok_or(Error::OutputIndex {
index: idx,
len: self.outputs.len(),
})?;
if matches!(self.reg(reg), RegVal::Unset) {
return Err(Error::UnsetRegister {
pos,
reg: reg.slot(),
});
}
let val = self.reg(reg).clone();
*self
.outputs
.get_mut(usize_idx)
.unwrap_or_else(|| unreachable!("usize_idx < outputs.len() checked above")) = val;
Ok(StepResult::Continue)
}
fn exec_add(&mut self, pos: usize) -> Result<StepResult, Error> {
let b = self.pop(pos)?;
let a = self.pop(pos)?;
self.push_stack(a.wrapping_add(b), pos)?;
Ok(StepResult::Continue)
}
fn exec_sub(&mut self, pos: usize) -> Result<StepResult, Error> {
let b = self.pop(pos)?;
let a = self.pop(pos)?;
self.push_stack(a.wrapping_sub(b), pos)?;
Ok(StepResult::Continue)
}
fn exec_mul(&mut self, pos: usize) -> Result<StepResult, Error> {
let b = self.pop(pos)?;
let a = self.pop(pos)?;
self.push_stack(a.wrapping_mul(b), pos)?;
Ok(StepResult::Continue)
}
fn exec_div(&mut self, pos: usize) -> Result<StepResult, Error> {
let b = self.pop(pos)?;
let a = self.pop(pos)?;
if b == 0 {
return Err(Error::DivisionByZero { pos });
}
let q = a.wrapping_div(b);
let r = a.wrapping_rem(b);
let floored = if r != 0 && (r ^ b) < 0 {
q.wrapping_sub(1)
} else {
q
};
self.push_stack(floored, pos)?;
Ok(StepResult::Continue)
}
fn exec_modulo(&mut self, pos: usize) -> Result<StepResult, Error> {
let b = self.pop(pos)?;
let a = self.pop(pos)?;
if b == 0 {
return Err(Error::DivisionByZero { pos });
}
let r = a.wrapping_rem(b);
let m = if r != 0 && (r ^ b) < 0 {
r.wrapping_add(b)
} else {
r
};
self.push_stack(m, pos)?;
Ok(StepResult::Continue)
}
fn exec_neg(&mut self, pos: usize) -> Result<StepResult, Error> {
let a = self.pop(pos)?;
self.push_stack(a.wrapping_neg(), pos)?;
Ok(StepResult::Continue)
}
fn exec_sqr(&mut self, pos: usize) -> Result<StepResult, Error> {
let a = self.pop(pos)?;
self.push_stack(a.wrapping_mul(a), pos)?;
Ok(StepResult::Continue)
}
fn exec_abs(&mut self, pos: usize) -> Result<StepResult, Error> {
let a = self.pop(pos)?;
self.push_stack(a.wrapping_abs(), pos)?;
Ok(StepResult::Continue)
}
fn exec_min(&mut self, pos: usize) -> Result<StepResult, Error> {
let b = self.pop(pos)?;
let a = self.pop(pos)?;
self.push_stack(a.min(b), pos)?;
Ok(StepResult::Continue)
}
fn exec_max(&mut self, pos: usize) -> Result<StepResult, Error> {
let b = self.pop(pos)?;
let a = self.pop(pos)?;
self.push_stack(a.max(b), pos)?;
Ok(StepResult::Continue)
}
fn exec_inc(&mut self, pos: usize) -> Result<StepResult, Error> {
let a = self.pop(pos)?;
self.push_stack(a.wrapping_add(1), pos)?;
Ok(StepResult::Continue)
}
fn exec_dec(&mut self, pos: usize) -> Result<StepResult, Error> {
let a = self.pop(pos)?;
self.push_stack(a.wrapping_sub(1), pos)?;
Ok(StepResult::Continue)
}
fn exec_eq(&mut self, pos: usize) -> Result<StepResult, Error> {
let b = self.pop(pos)?;
let a = self.pop(pos)?;
self.push_stack(i64::from(a == b), pos)?;
Ok(StepResult::Continue)
}
fn exec_lt(&mut self, pos: usize) -> Result<StepResult, Error> {
let b = self.pop(pos)?;
let a = self.pop(pos)?;
self.push_stack(i64::from(a < b), pos)?;
Ok(StepResult::Continue)
}
fn exec_gt(&mut self, pos: usize) -> Result<StepResult, Error> {
let b = self.pop(pos)?;
let a = self.pop(pos)?;
self.push_stack(i64::from(a > b), pos)?;
Ok(StepResult::Continue)
}
fn exec_lte(&mut self, pos: usize) -> Result<StepResult, Error> {
let b = self.pop(pos)?;
let a = self.pop(pos)?;
self.push_stack(i64::from(a <= b), pos)?;
Ok(StepResult::Continue)
}
fn exec_gte(&mut self, pos: usize) -> Result<StepResult, Error> {
let b = self.pop(pos)?;
let a = self.pop(pos)?;
self.push_stack(i64::from(a >= b), pos)?;
Ok(StepResult::Continue)
}
fn exec_not(&mut self, pos: usize) -> Result<StepResult, Error> {
let a = self.pop(pos)?;
self.push_stack(i64::from(a == 0), pos)?;
Ok(StepResult::Continue)
}
fn exec_and(&mut self, pos: usize) -> Result<StepResult, Error> {
let b = self.pop(pos)?;
let a = self.pop(pos)?;
self.push_stack(i64::from(a != 0 && b != 0), pos)?;
Ok(StepResult::Continue)
}
fn exec_or(&mut self, pos: usize) -> Result<StepResult, Error> {
let b = self.pop(pos)?;
let a = self.pop(pos)?;
self.push_stack(i64::from(a != 0 || b != 0), pos)?;
Ok(StepResult::Continue)
}
fn exec_xor(&mut self, pos: usize) -> Result<StepResult, Error> {
let b = self.pop(pos)?;
let a = self.pop(pos)?;
self.push_stack(i64::from((a != 0) ^ (b != 0)), pos)?;
Ok(StepResult::Continue)
}
fn exec_b_and(&mut self, pos: usize) -> Result<StepResult, Error> {
let b = self.pop(pos)?;
let a = self.pop(pos)?;
self.push_stack(a & b, pos)?;
Ok(StepResult::Continue)
}
fn exec_b_or(&mut self, pos: usize) -> Result<StepResult, Error> {
let b = self.pop(pos)?;
let a = self.pop(pos)?;
self.push_stack(a | b, pos)?;
Ok(StepResult::Continue)
}
fn exec_b_xor(&mut self, pos: usize) -> Result<StepResult, Error> {
let b = self.pop(pos)?;
let a = self.pop(pos)?;
self.push_stack(a ^ b, pos)?;
Ok(StepResult::Continue)
}
fn exec_b_not(&mut self, pos: usize) -> Result<StepResult, Error> {
let a = self.pop(pos)?;
self.push_stack(!a, pos)?;
Ok(StepResult::Continue)
}
fn exec_shl(&mut self, pos: usize) -> Result<StepResult, Error> {
let b = self.pop(pos)?;
let a = self.pop(pos)?;
if !(0..64).contains(&b) {
return Err(Error::InvalidShift { pos, amount: b });
}
self.push_stack(a << b, pos)?;
Ok(StepResult::Continue)
}
fn exec_shr(&mut self, pos: usize) -> Result<StepResult, Error> {
let b = self.pop(pos)?;
let a = self.pop(pos)?;
if !(0..64).contains(&b) {
return Err(Error::InvalidShift { pos, amount: b });
}
self.push_stack(a >> b, pos)?;
Ok(StepResult::Continue)
}
fn exec_bqmx(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
let size = usize::try_from(self.pop(pos)?).unwrap_or(0);
*self.reg_mut(reg) = RegVal::Model(XqmxModel::new(Domain::Binary, size));
Ok(StepResult::Continue)
}
fn exec_sqmx(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
let size = usize::try_from(self.pop(pos)?).unwrap_or(0);
*self.reg_mut(reg) = RegVal::Model(XqmxModel::new(Domain::Spin, size));
Ok(StepResult::Continue)
}
fn exec_xqmx(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
let k = self.pop(pos)?;
let size = usize::try_from(self.pop(pos)?).unwrap_or(0);
if k < 2 {
return Err(Error::InvalidDiscreteK { pos, k });
}
*self.reg_mut(reg) = RegVal::Model(XqmxModel::new(Domain::Discrete(k), size));
Ok(StepResult::Continue)
}
fn exec_bsmx(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
let size = usize::try_from(self.pop(pos)?).unwrap_or(0);
*self.reg_mut(reg) = RegVal::Sample(XqmxSample::new(Domain::Binary, vec![0; size]));
Ok(StepResult::Continue)
}
fn exec_ssmx(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
let size = usize::try_from(self.pop(pos)?).unwrap_or(0);
*self.reg_mut(reg) = RegVal::Sample(XqmxSample::new(Domain::Spin, vec![-1; size]));
Ok(StepResult::Continue)
}
fn exec_xsmx(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
let k = self.pop(pos)?;
let size = usize::try_from(self.pop(pos)?).unwrap_or(0);
if k < 2 {
return Err(Error::InvalidDiscreteK { pos, k });
}
*self.reg_mut(reg) = RegVal::Sample(XqmxSample::new(Domain::Discrete(k), vec![0; size]));
Ok(StepResult::Continue)
}
#[expect(
clippy::unnecessary_wraps,
reason = "dispatch macro requires Result<StepResult, Error> for all exec methods"
)]
fn exec_vec(&mut self, _pos: usize, reg: Register) -> Result<StepResult, Error> {
*self.reg_mut(reg) = RegVal::VecInt(Vec::new());
Ok(StepResult::Continue)
}
#[expect(
clippy::unnecessary_wraps,
reason = "dispatch macro requires Result<StepResult, Error> for all exec methods"
)]
fn exec_vec_i(&mut self, _pos: usize, reg: Register) -> Result<StepResult, Error> {
*self.reg_mut(reg) = RegVal::VecInt(Vec::new());
Ok(StepResult::Continue)
}
#[expect(
clippy::unnecessary_wraps,
reason = "dispatch macro requires Result<StepResult, Error> for all exec methods"
)]
fn exec_vec_x(&mut self, _pos: usize, reg: Register) -> Result<StepResult, Error> {
*self.reg_mut(reg) = RegVal::VecXqmx(Vec::new());
Ok(StepResult::Continue)
}
fn exec_vec_push(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
let v = self.pop(pos)?;
let vec = self
.reg_mut(reg)
.as_vec_int_mut()
.map_err(|got| Error::RegisterType {
reg: reg.slot(),
expected: "vec<int>",
got,
})?;
vec.push(v);
Ok(StepResult::Continue)
}
fn exec_vec_get(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
let idx = self.pop(pos)?;
let vec = self
.reg(reg)
.as_vec_int()
.map_err(|got| Error::RegisterType {
reg: reg.slot(),
expected: "vec<int>",
got,
})?;
let usize_idx = usize::try_from(idx).ok().filter(|&i| i < vec.len()).ok_or(
Error::IndexOutOfBounds {
pos,
index: idx,
len: vec.len(),
},
)?;
self.push_stack(
*vec.get(usize_idx)
.unwrap_or_else(|| unreachable!("usize_idx < vec.len() checked above")),
pos,
)?;
Ok(StepResult::Continue)
}
fn exec_vec_set(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
let val = self.pop(pos)?;
let idx = self.pop(pos)?;
let vec = self
.reg_mut(reg)
.as_vec_int_mut()
.map_err(|got| Error::RegisterType {
reg: reg.slot(),
expected: "vec<int>",
got,
})?;
let usize_idx = usize::try_from(idx).ok().filter(|&i| i < vec.len()).ok_or(
Error::IndexOutOfBounds {
pos,
index: idx,
len: vec.len(),
},
)?;
*vec.get_mut(usize_idx)
.unwrap_or_else(|| unreachable!("usize_idx < vec.len() checked above")) = val;
Ok(StepResult::Continue)
}
fn exec_vec_len(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
let len = match self.reg(reg) {
RegVal::VecInt(v) => v.len(),
RegVal::VecXqmx(v) => v.len(),
other => {
return Err(Error::RegisterType {
reg: reg.slot(),
expected: "vec",
got: other.type_name(),
});
}
};
self.push_stack(i64::try_from(len).unwrap_or(i64::MAX), pos)?;
Ok(StepResult::Continue)
}
fn exec_idx_grid(&mut self, pos: usize) -> Result<StepResult, Error> {
let cols = self.pop(pos)?;
let col = self.pop(pos)?;
let row = self.pop(pos)?;
self.push_stack(row.wrapping_mul(cols).wrapping_add(col), pos)?;
Ok(StepResult::Continue)
}
fn exec_idx_triu(&mut self, pos: usize) -> Result<StepResult, Error> {
let j = self.pop(pos)?;
let i = self.pop(pos)?;
let idx = j.wrapping_mul(j.wrapping_sub(1)) / 2 + i;
self.push_stack(idx, pos)?;
Ok(StepResult::Continue)
}
fn exec_get_line(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
let i = self.pop(pos)?;
let grid = self
.reg(reg)
.as_xqmx_grid()
.map_err(|got| Error::RegisterType {
reg: reg.slot(),
expected: "model|sample",
got,
})?;
let size = grid.size();
let usize_i = usize::try_from(i).map_err(|_| Error::IndexOutOfBounds {
pos,
index: i,
len: size,
})?;
if usize_i >= size {
return Err(Error::IndexOutOfBounds {
pos,
index: i,
len: size,
});
}
self.push_stack(grid.linear(usize_i), pos)?;
Ok(StepResult::Continue)
}
fn exec_set_line(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
let val = self.pop(pos)?;
let i = self.pop(pos)?;
let mut grid = self
.reg_mut(reg)
.as_xqmx_grid_mut()
.map_err(|got| Error::RegisterType {
reg: reg.slot(),
expected: "model|sample",
got,
})?;
let size = grid.size();
let usize_i = usize::try_from(i).map_err(|_| Error::IndexOutOfBounds {
pos,
index: i,
len: size,
})?;
if usize_i >= size {
return Err(Error::IndexOutOfBounds {
pos,
index: i,
len: size,
});
}
grid.linear_set(usize_i, val);
Ok(StepResult::Continue)
}
fn exec_add_line(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
let delta = self.pop(pos)?;
let i = self.pop(pos)?;
let mut grid = self
.reg_mut(reg)
.as_xqmx_grid_mut()
.map_err(|got| Error::RegisterType {
reg: reg.slot(),
expected: "model|sample",
got,
})?;
let size = grid.size();
let usize_i = usize::try_from(i).map_err(|_| Error::IndexOutOfBounds {
pos,
index: i,
len: size,
})?;
if usize_i >= size {
return Err(Error::IndexOutOfBounds {
pos,
index: i,
len: size,
});
}
grid.linear_add(usize_i, delta);
Ok(StepResult::Continue)
}
fn exec_get_quad(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
let j = self.pop(pos)?;
let i = self.pop(pos)?;
let m = self
.reg(reg)
.as_model()
.map_err(|got| Error::RegisterType {
reg: reg.slot(),
expected: "model",
got,
})?;
let usize_i = usize::try_from(i).map_err(|_| Error::IndexOutOfBounds {
pos,
index: i,
len: m.size,
})?;
let usize_j = usize::try_from(j).map_err(|_| Error::IndexOutOfBounds {
pos,
index: j,
len: m.size,
})?;
self.push_stack(m.get_quad(usize_i, usize_j), pos)?;
Ok(StepResult::Continue)
}
fn exec_set_quad(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
let val = self.pop(pos)?;
let j = self.pop(pos)?;
let i = self.pop(pos)?;
let m = self
.reg_mut(reg)
.as_model_mut()
.map_err(|got| Error::RegisterType {
reg: reg.slot(),
expected: "model",
got,
})?;
let usize_i = usize::try_from(i).map_err(|_| Error::IndexOutOfBounds {
pos,
index: i,
len: m.size,
})?;
let usize_j = usize::try_from(j).map_err(|_| Error::IndexOutOfBounds {
pos,
index: j,
len: m.size,
})?;
m.set_quad(usize_i, usize_j, val);
Ok(StepResult::Continue)
}
fn exec_add_quad(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
let delta = self.pop(pos)?;
let j = self.pop(pos)?;
let i = self.pop(pos)?;
let m = self
.reg_mut(reg)
.as_model_mut()
.map_err(|got| Error::RegisterType {
reg: reg.slot(),
expected: "model",
got,
})?;
let usize_i = usize::try_from(i).map_err(|_| Error::IndexOutOfBounds {
pos,
index: i,
len: m.size,
})?;
let usize_j = usize::try_from(j).map_err(|_| Error::IndexOutOfBounds {
pos,
index: j,
len: m.size,
})?;
m.add_quad(usize_i, usize_j, delta);
Ok(StepResult::Continue)
}
fn exec_resize(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
let cols = self.pop(pos)?;
let rows = self.pop(pos)?;
if rows <= 0 || cols <= 0 {
return Err(Error::InvalidGridDimensions { pos, rows, cols });
}
let usize_rows =
usize::try_from(rows).map_err(|_| Error::InvalidGridDimensions { pos, rows, cols })?;
let usize_cols =
usize::try_from(cols).map_err(|_| Error::InvalidGridDimensions { pos, rows, cols })?;
let mut grid = self
.reg_mut(reg)
.as_xqmx_grid_mut()
.map_err(|got| Error::RegisterType {
reg: reg.slot(),
expected: "model|sample",
got,
})?;
grid.set_grid(usize_rows, usize_cols);
Ok(StepResult::Continue)
}
fn exec_row_find(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
let value = self.pop(pos)?;
let row = self.pop(pos)?;
let grid = self
.reg(reg)
.as_xqmx_grid()
.map_err(|got| Error::RegisterType {
reg: reg.slot(),
expected: "model|sample",
got,
})?;
let usize_row = usize::try_from(row).map_err(|_| Error::IndexOutOfBounds {
pos,
index: row,
len: grid.rows(),
})?;
let cols = grid.cols();
let row_start = usize_row * cols;
let result = (0..cols)
.find(|&col| grid.linear(row_start + col) == value)
.map_or(-1, |c| i64::try_from(c).unwrap_or(-1));
self.push_stack(result, pos)?;
Ok(StepResult::Continue)
}
fn exec_col_find(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
let value = self.pop(pos)?;
let col = self.pop(pos)?;
let grid = self
.reg(reg)
.as_xqmx_grid()
.map_err(|got| Error::RegisterType {
reg: reg.slot(),
expected: "model|sample",
got,
})?;
let usize_col = usize::try_from(col).map_err(|_| Error::IndexOutOfBounds {
pos,
index: col,
len: grid.cols(),
})?;
let rows = grid.rows();
let cols = grid.cols();
let result = (0..rows)
.find(|&row| grid.linear(row * cols + usize_col) == value)
.map_or(-1, |r| i64::try_from(r).unwrap_or(-1));
self.push_stack(result, pos)?;
Ok(StepResult::Continue)
}
fn exec_row_sum(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
let row = self.pop(pos)?;
let grid = self
.reg(reg)
.as_xqmx_grid()
.map_err(|got| Error::RegisterType {
reg: reg.slot(),
expected: "model|sample",
got,
})?;
let usize_row = usize::try_from(row).map_err(|_| Error::IndexOutOfBounds {
pos,
index: row,
len: grid.rows(),
})?;
let cols = grid.cols();
let row_start = usize_row * cols;
let sum: i64 = (0..cols).map(|c| grid.linear(row_start + c)).sum();
self.push_stack(sum, pos)?;
Ok(StepResult::Continue)
}
fn exec_col_sum(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
let col = self.pop(pos)?;
let grid = self
.reg(reg)
.as_xqmx_grid()
.map_err(|got| Error::RegisterType {
reg: reg.slot(),
expected: "model|sample",
got,
})?;
let usize_col = usize::try_from(col).map_err(|_| Error::IndexOutOfBounds {
pos,
index: col,
len: grid.cols(),
})?;
let rows = grid.rows();
let cols = grid.cols();
let sum: i64 = (0..rows).map(|r| grid.linear(r * cols + usize_col)).sum();
self.push_stack(sum, pos)?;
Ok(StepResult::Continue)
}
fn exec_one_hot_r(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
let penalty = self.pop(pos)?;
let row = self.pop(pos)?;
let m = self
.reg_mut(reg)
.as_model_mut()
.map_err(|got| Error::RegisterType {
reg: reg.slot(),
expected: "model",
got,
})?;
let usize_row = usize::try_from(row).map_err(|_| Error::IndexOutOfBounds {
pos,
index: row,
len: m.rows,
})?;
let row_start = usize_row * m.cols;
for c in 0..m.cols {
m.add_linear(row_start + c, -penalty);
}
for ci in 0..m.cols {
for cj in (ci + 1)..m.cols {
m.add_quad(row_start + ci, row_start + cj, 2 * penalty);
}
}
Ok(StepResult::Continue)
}
fn exec_one_hot_c(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
let penalty = self.pop(pos)?;
let col = self.pop(pos)?;
let m = self
.reg_mut(reg)
.as_model_mut()
.map_err(|got| Error::RegisterType {
reg: reg.slot(),
expected: "model",
got,
})?;
let col_idx = usize::try_from(col).map_err(|_| Error::IndexOutOfBounds {
pos,
index: col,
len: m.cols,
})?;
for ri in 0..m.rows {
m.add_linear(ri * m.cols + col_idx, -penalty);
}
for ri in 0..m.rows {
for rj in (ri + 1)..m.rows {
m.add_quad(ri * m.cols + col_idx, rj * m.cols + col_idx, 2 * penalty);
}
}
Ok(StepResult::Continue)
}
fn exec_exclude(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
let penalty = self.pop(pos)?;
let j = self.pop(pos)?;
let i = self.pop(pos)?;
let m = self
.reg_mut(reg)
.as_model_mut()
.map_err(|got| Error::RegisterType {
reg: reg.slot(),
expected: "model",
got,
})?;
let i_idx = usize::try_from(i).map_err(|_| Error::IndexOutOfBounds {
pos,
index: i,
len: m.size,
})?;
let j_idx = usize::try_from(j).map_err(|_| Error::IndexOutOfBounds {
pos,
index: j,
len: m.size,
})?;
m.add_quad(i_idx, j_idx, penalty);
Ok(StepResult::Continue)
}
fn exec_implies(&mut self, pos: usize, reg: Register) -> Result<StepResult, Error> {
let penalty = self.pop(pos)?;
let j = self.pop(pos)?;
let i = self.pop(pos)?;
let m = self
.reg_mut(reg)
.as_model_mut()
.map_err(|got| Error::RegisterType {
reg: reg.slot(),
expected: "model",
got,
})?;
let i_idx = usize::try_from(i).map_err(|_| Error::IndexOutOfBounds {
pos,
index: i,
len: m.size,
})?;
let j_idx = usize::try_from(j).map_err(|_| Error::IndexOutOfBounds {
pos,
index: j,
len: m.size,
})?;
m.add_linear(i_idx, penalty);
m.add_quad(i_idx, j_idx, -penalty);
Ok(StepResult::Continue)
}
fn exec_energy(
&mut self,
pos: usize,
model: Register,
sample: Register,
) -> Result<StepResult, Error> {
let sample_values: Vec<i64> = match self.reg(sample) {
RegVal::Sample(s) => s.values.clone(),
other => {
return Err(Error::RegisterType {
reg: sample.slot(),
expected: "sample",
got: other.type_name(),
});
}
};
let m = match self.reg(model) {
RegVal::Model(m) => m,
other => {
return Err(Error::RegisterType {
reg: model.slot(),
expected: "model",
got: other.type_name(),
});
}
};
let energy = m.energy(&sample_values)?;
self.push_stack(energy, pos)?;
Ok(StepResult::Continue)
}
}
#[cfg(test)]
mod tests {
extern crate alloc;
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use crate::bytecode::{Instruction, InstructionBuilder, Register};
use crate::Vm;
use crate::error::Error;
use crate::tracer::{NoopTracer, StepState, Tracer};
use crate::value::RegVal;
struct RecordingTracer {
steps: Vec<RecordedStep>,
}
#[expect(
dead_code,
reason = "struct fields are available for debugging inspections"
)]
struct RecordedStep {
pos: usize,
step: u64,
instruction: Instruction,
stack: Vec<i64>,
read_regs: Vec<(u8, RegVal)>,
written_regs: Vec<(u8, RegVal)>,
loop_depth: usize,
}
impl RecordingTracer {
fn new() -> Self {
Self { steps: Vec::new() }
}
}
impl Tracer for RecordingTracer {
type Error = core::convert::Infallible;
fn on_step(&mut self, state: &StepState<'_>) -> Result<(), Self::Error> {
self.steps.push(RecordedStep {
pos: state.pos,
step: state.step,
instruction: *state.instruction,
stack: state.stack.to_vec(),
read_regs: state.read_regs.to_vec(),
written_regs: state.written_regs.to_vec(),
loop_depth: state.loop_depth,
});
Ok(())
}
}
struct FailingTracer {
fail_at: u64,
}
impl Tracer for FailingTracer {
type Error = String;
fn on_step(&mut self, state: &StepState<'_>) -> Result<(), Self::Error> {
if state.step == self.fail_at {
Err(format!("intentional failure at step {}", self.fail_at))
} else {
Ok(())
}
}
}
#[test]
fn failing_tracer_propagates_error() {
let mut b = InstructionBuilder::new();
let _ = b.emit_push(1).emit_halt();
let program = b.build().unwrap();
let mut vm = Vm::new();
let mut tracer = FailingTracer { fail_at: 1 };
let err = vm.run_trace(&mut tracer, &program).unwrap_err();
assert!(
matches!(err, Error::TraceFailed { .. }),
"expected TraceFailed, got {err:?}",
);
}
#[test]
fn run_delegates_to_run_trace() {
let mut b = InstructionBuilder::new();
let _ = b.emit_push(3).emit_push(4).emit_add().emit_halt();
let program = b.build().unwrap();
let mut vm1 = Vm::new();
vm1.run(&program).unwrap();
let mut vm2 = Vm::new();
vm2.run_trace(&mut NoopTracer, &program).unwrap();
assert_eq!(vm1.stack(), vm2.stack());
}
#[test]
fn recording_tracer_captures_steps() {
let mut b = InstructionBuilder::new();
let _ = b
.emit_push(3)
.emit_push(4)
.emit_add()
.emit_stow(Register(0))
.emit_halt();
let program = b.build().unwrap();
let mut tracer = RecordingTracer::new();
let mut vm = Vm::new();
vm.run_trace(&mut tracer, &program).unwrap();
assert_eq!(tracer.steps.len(), 5);
let s0 = tracer.steps.first().expect("step 0");
assert_eq!(s0.step, 1);
assert_eq!(s0.stack, &[3]);
assert!(s0.read_regs.is_empty());
assert!(s0.written_regs.is_empty());
let s1 = tracer.steps.get(1).expect("step 1");
assert_eq!(s1.stack, &[3, 4]);
let s2 = tracer.steps.get(2).expect("step 2");
assert_eq!(s2.stack, &[7]);
let s3 = tracer.steps.get(3).expect("step 3");
assert_eq!(s3.stack, &[] as &[i64]);
assert!(s3.read_regs.is_empty());
assert_eq!(s3.written_regs.len(), 1);
assert_eq!(s3.written_regs.first(), Some(&(0, RegVal::Int(7))));
let s4 = tracer.steps.get(4).expect("step 4");
assert_eq!(s4.stack, &[] as &[i64]);
}
#[test]
fn recording_tracer_captures_read_regs() {
let mut b = InstructionBuilder::new();
let _ = b
.emit_push(42)
.emit_stow(Register(0))
.emit_load(Register(0))
.emit_halt();
let program = b.build().unwrap();
let mut tracer = RecordingTracer::new();
let mut vm = Vm::new();
vm.run_trace(&mut tracer, &program).unwrap();
let s2 = tracer.steps.get(2).expect("step 2");
assert_eq!(s2.read_regs.len(), 1);
assert_eq!(s2.read_regs.first(), Some(&(0, RegVal::Int(42))));
}
#[test]
fn div_floor_negative_dividend() {
let mut b = InstructionBuilder::new();
let _ = b.emit_push(-7).emit_push(2).emit_div().emit_halt();
let program = b.build().unwrap();
let mut vm = Vm::new();
vm.run(&program).unwrap();
assert_eq!(vm.stack(), &[-4]);
}
#[test]
fn div_floor_negative_divisor() {
let mut b = InstructionBuilder::new();
let _ = b.emit_push(7).emit_push(-2).emit_div().emit_halt();
let program = b.build().unwrap();
let mut vm = Vm::new();
vm.run(&program).unwrap();
assert_eq!(vm.stack(), &[-4]);
}
#[test]
fn div_floor_both_positive() {
let mut b = InstructionBuilder::new();
let _ = b.emit_push(7).emit_push(2).emit_div().emit_halt();
let program = b.build().unwrap();
let mut vm = Vm::new();
vm.run(&program).unwrap();
assert_eq!(vm.stack(), &[3]);
}
#[test]
fn mod_divisor_sign_negative_dividend() {
let mut b = InstructionBuilder::new();
let _ = b.emit_push(-7).emit_push(2).emit_modulo().emit_halt();
let program = b.build().unwrap();
let mut vm = Vm::new();
vm.run(&program).unwrap();
assert_eq!(vm.stack(), &[1]);
}
#[test]
fn mod_divisor_sign_negative_divisor() {
let mut b = InstructionBuilder::new();
let _ = b.emit_push(7).emit_push(-2).emit_modulo().emit_halt();
let program = b.build().unwrap();
let mut vm = Vm::new();
vm.run(&program).unwrap();
assert_eq!(vm.stack(), &[-1]);
}
#[test]
fn mod_divisor_sign_both_positive() {
let mut b = InstructionBuilder::new();
let _ = b.emit_push(7).emit_push(3).emit_modulo().emit_halt();
let program = b.build().unwrap();
let mut vm = Vm::new();
vm.run(&program).unwrap();
assert_eq!(vm.stack(), &[1]);
}
#[test]
fn load_on_never_set_register_faults() {
let mut b = InstructionBuilder::new();
let _ = b.emit_load(Register(5)).emit_halt();
let program = b.build().unwrap();
let mut vm = Vm::new();
let err = vm.run(&program).unwrap_err();
assert!(
matches!(err, Error::UnsetRegister { reg: 5, .. }),
"expected UnsetRegister, got {err:?}"
);
}
#[test]
fn drop_then_load_faults() {
let mut b = InstructionBuilder::new();
let _ = b
.emit_push(42)
.emit_stow(Register(0))
.emit_drop(Register(0))
.emit_load(Register(0))
.emit_halt();
let program = b.build().unwrap();
let mut vm = Vm::new();
let err = vm.run(&program).unwrap_err();
assert!(
matches!(err, Error::UnsetRegister { reg: 0, .. }),
"expected UnsetRegister, got {err:?}"
);
}
#[test]
fn output_on_unset_register_faults() {
let mut b = InstructionBuilder::new();
let _ = b.emit_push(0).emit_output(Register(7)).emit_halt();
let program = b.build().unwrap();
let mut vm = Vm::new();
let _ = vm.set_output_slots(1);
let err = vm.run(&program).unwrap_err();
assert!(
matches!(err, Error::UnsetRegister { reg: 7, .. }),
"expected UnsetRegister, got {err:?}"
);
}
#[test]
fn stow_then_load_works_after_unset_init() {
let mut b = InstructionBuilder::new();
let _ = b
.emit_push(99)
.emit_stow(Register(3))
.emit_load(Register(3))
.emit_halt();
let program = b.build().unwrap();
let mut vm = Vm::new();
vm.run(&program).unwrap();
assert_eq!(vm.stack(), &[99]);
}
}