#[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 },
SkipLoop,
}
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 });
}
StepResult::SkipLoop => {
let mut depth: u32 = 1;
loop {
let Some(item) = stream.next_instruction() else {
return Err(Error::UnmatchedLoop { pos });
};
let (_scan_pos, _label, scan_instr) = item.map_err(Error::from)?;
match scan_instr {
Instruction::Range {} | Instruction::Iter { .. } => depth += 1,
Instruction::Next {} => {
depth -= 1;
if depth == 0 {
break;
}
}
_ => {}
}
}
}
}
}
Ok(())
}
}
macro_rules! impl_dispatch {
(
$( ($code:literal, $variant:ident, $mnem:literal, $doc:literal,
$_delta:expr, {$($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)?;
if count <= 0 {
return Ok(StepResult::SkipLoop);
}
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.kind().kind_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(|e| Error::RegisterType {
reg: reg.slot(),
expected: "int",
got: e.actual.kind_name(),
})?;
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_bit_len(&mut self, pos: usize) -> Result<StepResult, Error> {
let a = self.pop(pos)?;
let result = if a > 0 {
i64::from(i64::BITS - a.leading_zeros())
} else {
0
};
self.push_stack(result, 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(|e| Error::RegisterType {
reg: reg.slot(),
expected: "vec<int>",
got: e.actual.kind_name(),
})?;
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(|e| Error::RegisterType {
reg: reg.slot(),
expected: "vec<int>",
got: e.actual.kind_name(),
})?;
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(|e| Error::RegisterType {
reg: reg.slot(),
expected: "vec<int>",
got: e.actual.kind_name(),
})?;
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.kind().kind_name(),
});
}
};
self.push_stack(i64::try_from(len).unwrap_or(i64::MAX), pos)?;
Ok(StepResult::Continue)
}
fn exec_slack(
&mut self,
pos: usize,
indices: Register,
coeffs: Register,
) -> Result<StepResult, Error> {
let capacity = self.pop(pos)?;
let start_index = self.pop(pos)?;
if capacity <= 0 {
return Ok(StepResult::Continue);
}
{
let vec = self
.reg_mut(indices)
.as_vec_int_mut()
.map_err(|e| Error::RegisterType {
reg: indices.slot(),
expected: "vec<int>",
got: e.actual.kind_name(),
})?;
let mut power = 1i64;
let mut i = 0i64;
while power > 0 && power <= capacity {
vec.push(start_index + i);
power = power.wrapping_mul(2);
i += 1;
}
}
{
let vec = self
.reg_mut(coeffs)
.as_vec_int_mut()
.map_err(|e| Error::RegisterType {
reg: coeffs.slot(),
expected: "vec<int>",
got: e.actual.kind_name(),
})?;
let mut power = 1i64;
while power > 0 && power <= capacity {
vec.push(power);
power = power.wrapping_mul(2);
}
}
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(|e| Error::RegisterType {
reg: reg.slot(),
expected: "model|sample",
got: e.actual.kind_name(),
})?;
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(|e| Error::RegisterType {
reg: reg.slot(),
expected: "model|sample",
got: e.actual.kind_name(),
})?;
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(|e| Error::RegisterType {
reg: reg.slot(),
expected: "model|sample",
got: e.actual.kind_name(),
})?;
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(|e| Error::RegisterType {
reg: reg.slot(),
expected: "model",
got: e.actual.kind_name(),
})?;
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(|e| Error::RegisterType {
reg: reg.slot(),
expected: "model",
got: e.actual.kind_name(),
})?;
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(|e| Error::RegisterType {
reg: reg.slot(),
expected: "model",
got: e.actual.kind_name(),
})?;
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(|e| Error::RegisterType {
reg: reg.slot(),
expected: "model|sample",
got: e.actual.kind_name(),
})?;
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(|e| Error::RegisterType {
reg: reg.slot(),
expected: "model|sample",
got: e.actual.kind_name(),
})?;
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(|e| Error::RegisterType {
reg: reg.slot(),
expected: "model|sample",
got: e.actual.kind_name(),
})?;
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(|e| Error::RegisterType {
reg: reg.slot(),
expected: "model|sample",
got: e.actual.kind_name(),
})?;
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(|e| Error::RegisterType {
reg: reg.slot(),
expected: "model|sample",
got: e.actual.kind_name(),
})?;
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(|e| Error::RegisterType {
reg: reg.slot(),
expected: "model",
got: e.actual.kind_name(),
})?;
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(|e| Error::RegisterType {
reg: reg.slot(),
expected: "model",
got: e.actual.kind_name(),
})?;
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(|e| Error::RegisterType {
reg: reg.slot(),
expected: "model",
got: e.actual.kind_name(),
})?;
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(|e| Error::RegisterType {
reg: reg.slot(),
expected: "model",
got: e.actual.kind_name(),
})?;
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_equality(
&mut self,
pos: usize,
model: Register,
indices: Register,
coeffs: Register,
) -> Result<StepResult, Error> {
let penalty = self.pop(pos)?;
let target = self.pop(pos)?;
let idx_vec: Vec<i64> = self
.reg(indices)
.as_vec_int()
.map_err(|e| Error::RegisterType {
reg: indices.slot(),
expected: "vec<int>",
got: e.actual.kind_name(),
})?
.clone();
let coeff_vec: Vec<i64> = self
.reg(coeffs)
.as_vec_int()
.map_err(|e| Error::RegisterType {
reg: coeffs.slot(),
expected: "vec<int>",
got: e.actual.kind_name(),
})?
.clone();
if idx_vec.len() != coeff_vec.len() {
return Err(Error::VecLengthMismatch {
what: "indices",
a: idx_vec.len(),
other: "coeffs",
b: coeff_vec.len(),
});
}
let m = self
.reg_mut(model)
.as_model_mut()
.map_err(|e| Error::RegisterType {
reg: model.slot(),
expected: "model",
got: e.actual.kind_name(),
})?;
if let Some(&max_idx) = idx_vec.iter().max()
&& let Ok(needed) = usize::try_from(max_idx + 1)
&& needed > m.size
{
m.size = needed;
}
let idx_us = indices_to_usize(&idx_vec, pos, m.size)?;
expand_equality(m, &idx_us, &coeff_vec, target, penalty);
Ok(StepResult::Continue)
}
fn exec_at_least(
&mut self,
pos: usize,
model: Register,
indices: Register,
) -> Result<StepResult, Error> {
let penalty = self.pop(pos)?;
let k = self.pop(pos)?;
let idx_vec: Vec<i64> = self
.reg(indices)
.as_vec_int()
.map_err(|e| Error::RegisterType {
reg: indices.slot(),
expected: "vec<int>",
got: e.actual.kind_name(),
})?
.clone();
let n = idx_vec.len();
let n_i64 = i64::try_from(n).unwrap_or(i64::MAX);
if k <= 0 || k > n_i64 {
return Err(Error::IndexOutOfBounds {
pos,
index: k,
len: n,
});
}
let m = self
.reg_mut(model)
.as_model_mut()
.map_err(|e| Error::RegisterType {
reg: model.slot(),
expected: "model",
got: e.actual.kind_name(),
})?;
let max_excess = n_i64 - k;
if max_excess <= 0 {
let unit_coeffs = vec![1i64; n];
let idx_us = indices_to_usize(&idx_vec, pos, m.size)?;
expand_equality(m, &idx_us, &unit_coeffs, k, penalty);
return Ok(StepResult::Continue);
}
let num_slacks = (i64::BITS - max_excess.leading_zeros()) as usize;
let slack_start = m.size;
let idx_us = indices_to_usize(&idx_vec, pos, slack_start)?;
m.size += num_slacks;
let mut all_indices = idx_us;
let mut all_coeffs = vec![1i64; n];
for i in 0..num_slacks {
all_indices.push(slack_start + i);
all_coeffs.push(-(1i64 << i));
}
expand_equality(m, &all_indices, &all_coeffs, k, penalty);
Ok(StepResult::Continue)
}
fn exec_at_least_w(
&mut self,
pos: usize,
model: Register,
indices: Register,
coeffs: Register,
) -> Result<StepResult, Error> {
let penalty = self.pop(pos)?;
let k = self.pop(pos)?;
let idx_vec: Vec<i64> = self
.reg(indices)
.as_vec_int()
.map_err(|e| Error::RegisterType {
reg: indices.slot(),
expected: "vec<int>",
got: e.actual.kind_name(),
})?
.clone();
let coeff_vec: Vec<i64> = self
.reg(coeffs)
.as_vec_int()
.map_err(|e| Error::RegisterType {
reg: coeffs.slot(),
expected: "vec<int>",
got: e.actual.kind_name(),
})?
.clone();
let n = idx_vec.len();
if n != coeff_vec.len() {
return Err(Error::VecLengthMismatch {
what: "indices",
a: n,
other: "coeffs",
b: coeff_vec.len(),
});
}
if k <= 0 {
return Err(Error::IndexOutOfBounds {
pos,
index: k,
len: n,
});
}
let m = self
.reg_mut(model)
.as_model_mut()
.map_err(|e| Error::RegisterType {
reg: model.slot(),
expected: "model",
got: e.actual.kind_name(),
})?;
let weight_sum: i64 = coeff_vec.iter().sum();
let max_excess = weight_sum - k;
if max_excess <= 0 {
let idx_us = indices_to_usize(&idx_vec, pos, m.size)?;
expand_equality(m, &idx_us, &coeff_vec, k, penalty);
return Ok(StepResult::Continue);
}
let num_slacks = (i64::BITS - max_excess.leading_zeros()) as usize;
let slack_start = m.size;
let idx_us = indices_to_usize(&idx_vec, pos, slack_start)?;
m.size += num_slacks;
let mut all_indices = idx_us;
let mut all_coeffs = coeff_vec.clone();
for i in 0..num_slacks {
all_indices.push(slack_start + i);
all_coeffs.push(-(1i64 << i));
}
expand_equality(m, &all_indices, &all_coeffs, k, penalty);
Ok(StepResult::Continue)
}
fn exec_reduce(&mut self, pos: usize, model: Register) -> Result<StepResult, Error> {
let p_aux = self.pop(pos)?;
let var_b = self.pop(pos)?;
let var_a = self.pop(pos)?;
let m = self
.reg_mut(model)
.as_model_mut()
.map_err(|e| Error::RegisterType {
reg: model.slot(),
expected: "model",
got: e.actual.kind_name(),
})?;
let ua =
usize::try_from(var_a)
.ok()
.filter(|&v| v < m.size)
.ok_or(Error::IndexOutOfBounds {
pos,
index: var_a,
len: m.size,
})?;
let ub =
usize::try_from(var_b)
.ok()
.filter(|&v| v < m.size)
.ok_or(Error::IndexOutOfBounds {
pos,
index: var_b,
len: m.size,
})?;
let w = expand_reduce(m, ua, ub, p_aux);
self.push_stack(i64::try_from(w).unwrap_or(i64::MAX), pos)?;
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.kind().kind_name(),
});
}
};
let m = match self.reg(model) {
RegVal::Model(m) => m,
other => {
return Err(Error::RegisterType {
reg: model.slot(),
expected: "model",
got: other.kind().kind_name(),
});
}
};
let energy = m.energy(&sample_values)?;
self.push_stack(energy, pos)?;
Ok(StepResult::Continue)
}
}
fn expand_equality(
model: &mut XqmxModel,
indices: &[usize],
coeffs: &[i64],
target: i64,
penalty: i64,
) {
let two_b = 2 * target;
for (&idx, &a_k) in indices.iter().zip(coeffs.iter()) {
model.add_linear(idx, penalty * a_k * (a_k - two_b));
}
let two_p = 2 * penalty;
for (k, (&idx_k, &a_k)) in indices.iter().zip(coeffs.iter()).enumerate() {
for (&idx_m, &a_m) in indices.iter().zip(coeffs.iter()).skip(k + 1) {
model.add_quad(idx_k, idx_m, two_p * a_k * a_m);
}
}
}
fn indices_to_usize(idxs: &[i64], pos: usize, model_size: usize) -> Result<Vec<usize>, Error> {
idxs.iter()
.map(|&i| {
usize::try_from(i)
.ok()
.filter(|&u| u < model_size)
.ok_or(Error::IndexOutOfBounds {
pos,
index: i,
len: model_size,
})
})
.collect()
}
fn expand_reduce(model: &mut XqmxModel, var_a: usize, var_b: usize, p_aux: i64) -> usize {
let w = model.size;
model.size += 1;
model.add_quad(var_a, var_b, p_aux);
model.add_quad(var_a, w, -2 * p_aux);
model.add_quad(var_b, w, -2 * p_aux);
model.add_linear(w, 3 * p_aux);
w
}
#[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]);
}
#[test]
fn range_count_zero_skips_body() {
let mut b = InstructionBuilder::new();
let _ = b
.emit_push(0)
.emit_push(0)
.emit_range()
.emit_push(99)
.emit_next()
.emit_push(42)
.emit_halt();
let program = b.build().unwrap();
let mut vm = Vm::new();
vm.run(&program).unwrap();
assert_eq!(vm.stack(), &[42]);
}
#[test]
fn range_count_negative_skips_body() {
let mut b = InstructionBuilder::new();
let _ = b
.emit_push(0)
.emit_push(-3)
.emit_range()
.emit_push(99)
.emit_next()
.emit_push(42)
.emit_halt();
let program = b.build().unwrap();
let mut vm = Vm::new();
vm.run(&program).unwrap();
assert_eq!(vm.stack(), &[42]);
}
#[test]
fn range_count_zero_nested_skips() {
let mut b = InstructionBuilder::new();
let _ = b
.emit_push(0)
.emit_push(0)
.emit_range() .emit_push(0)
.emit_push(3)
.emit_range() .emit_push(99)
.emit_next() .emit_next() .emit_push(42)
.emit_halt();
let program = b.build().unwrap();
let mut vm = Vm::new();
vm.run(&program).unwrap();
assert_eq!(vm.stack(), &[42]);
}
#[test]
fn range_count_one_executes_body_once() {
let mut b = InstructionBuilder::new();
let _ = b
.emit_push(0)
.emit_push(0)
.emit_stow(Register(0)) .emit_push(5) .emit_push(1) .emit_range()
.emit_load(Register(0))
.emit_inc()
.emit_stow(Register(0)) .emit_next()
.emit_halt();
let program = b.build().unwrap();
let mut vm = Vm::new();
vm.run(&program).unwrap();
assert_eq!(*vm.reg(Register(0)), RegVal::Int(1));
}
#[test]
fn range_positive_nested_still_works() {
let mut b = InstructionBuilder::new();
let _ = b
.emit_push(0)
.emit_stow(Register(0)) .emit_push(0)
.emit_push(2)
.emit_range() .emit_push(0)
.emit_push(3)
.emit_range() .emit_load(Register(0))
.emit_inc()
.emit_stow(Register(0))
.emit_next() .emit_next() .emit_halt();
let program = b.build().unwrap();
let mut vm = Vm::new();
vm.run(&program).unwrap();
assert_eq!(*vm.reg(Register(0)), RegVal::Int(6));
}
#[test]
fn range_count_zero_unmatched_faults() {
let mut b = InstructionBuilder::new();
let _ = b.emit_push(0).emit_push(0).emit_range().emit_halt();
let program = b.build().unwrap();
let mut vm = Vm::new();
let err = vm.run(&program).unwrap_err();
assert!(
matches!(err, Error::UnmatchedLoop { .. }),
"expected UnmatchedLoop, got {err:?}"
);
}
}