198 lines
7.1 KiB
Rust
198 lines
7.1 KiB
Rust
#![no_std]
|
|
#![forbid(unsafe_code)]
|
|
|
|
extern crate alloc;
|
|
|
|
use alloc::vec::Vec;
|
|
use core::fmt;
|
|
use fogtix_bytecode::{consts, is_call2jump, next_instr, Instr};
|
|
|
|
pub type StackEntValue = u64;
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum Error<'m> {
|
|
UnparsableInstruction(&'m [u8]),
|
|
OutOfFuel,
|
|
NotEnoughStacked,
|
|
DivisionByZero,
|
|
}
|
|
|
|
impl fmt::Display for Error<'_> {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Self::UnparsableInstruction(x) => write!(f, "reached unparsable instruction: {:x?}", x),
|
|
Self::OutOfFuel => write!(f, "out of fuel"),
|
|
Self::NotEnoughStacked => write!(f, "not enough operands on stack"),
|
|
Self::DivisionByZero => write!(f, "tried to divide by zero"),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct Process<'m> {
|
|
pub stack: Vec<StackEntValue>,
|
|
pub m: &'m [u8],
|
|
pub callstack: Vec<usize>,
|
|
pub instrp: usize,
|
|
}
|
|
|
|
impl<'m> Process<'m> {
|
|
fn stpop(&mut self) -> Result<StackEntValue, Error<'m>> {
|
|
self.stack.pop().ok_or(Error::NotEnoughStacked)
|
|
}
|
|
|
|
pub fn run(&mut self, mut fuel: Option<&mut u64>) -> Result<Option<(u16, u64)>, Error<'m>> {
|
|
loop {
|
|
let previptr = self.instrp;
|
|
tracing::trace!("previptr = {}", previptr);
|
|
if let Some(ref mut x) = fuel {
|
|
if **x == 0 {
|
|
return Err(Error::OutOfFuel);
|
|
}
|
|
**x -= 1;
|
|
}
|
|
let (nxtidelta, nxti) = next_instr(self.m, self.instrp)
|
|
.unwrap()
|
|
.map_err(Error::UnparsableInstruction)?;
|
|
self.instrp += nxtidelta;
|
|
match nxti {
|
|
Instr::CallRemote(intp) => {
|
|
if !is_call2jump(self.m, self.instrp) {
|
|
self.callstack.push(self.instrp);
|
|
}
|
|
return Ok(Some((intp, self.stpop()?)));
|
|
}
|
|
Instr::CallLocal(x) => {
|
|
if !is_call2jump(self.m, self.instrp) {
|
|
self.callstack.push(self.instrp);
|
|
}
|
|
self.instrp = x.try_into().unwrap();
|
|
}
|
|
Instr::CallLDefer(x) => {
|
|
let jtip = x.try_into().unwrap();
|
|
if is_call2jump(self.m, self.instrp) {
|
|
self.instrp = jtip;
|
|
} else {
|
|
self.callstack.push(jtip);
|
|
}
|
|
}
|
|
Instr::JumpCond(x) => {
|
|
if self.stpop()? != 0 {
|
|
self.instrp = x.try_into().unwrap();
|
|
}
|
|
}
|
|
Instr::Return => match self.callstack.pop() {
|
|
Some(x) => self.instrp = x,
|
|
None => return Ok(None),
|
|
},
|
|
Instr::Push(v) => self.stack.push(v),
|
|
Instr::Pop(_) if self.stack.is_empty() => return Err(Error::NotEnoughStacked),
|
|
Instr::Pop(cnt) => {
|
|
let ssl = self.stack.len() - 1;
|
|
let cnt = usize::from(cnt);
|
|
if cnt >= ssl {
|
|
self.stack = Vec::new();
|
|
} else {
|
|
self.stack.truncate(ssl - cnt - 1);
|
|
}
|
|
}
|
|
Instr::Dup(delta) => {
|
|
let x = match self.stack.len().checked_sub(usize::from(delta) + 1) {
|
|
None => return Err(Error::NotEnoughStacked),
|
|
// SAFETY: the value x is always smaller than the stack height
|
|
Some(x) => self.stack[x],
|
|
};
|
|
self.stack.push(x);
|
|
}
|
|
Instr::Swap(delta) => {
|
|
let ssl = self.stack.len();
|
|
let (y, z) = match ssl.checked_sub(usize::from(delta) + 2) {
|
|
None => return Err(Error::NotEnoughStacked),
|
|
Some(ltrg) => self.stack[ltrg..].split_at_mut(1),
|
|
};
|
|
core::mem::swap(&mut y[0], &mut z[0]);
|
|
}
|
|
Instr::Shift(shdelta) => {
|
|
let x = self.stack.last_mut().ok_or(Error::NotEnoughStacked)?;
|
|
if shdelta < 0 {
|
|
*x <<= (-shdelta) as u8;
|
|
} else {
|
|
*x >>= (shdelta as u8) + 1;
|
|
}
|
|
}
|
|
Instr::DupFrom => {
|
|
let x: u64 = self.stack.pop().ok_or(Error::NotEnoughStacked)?;
|
|
let y: u64 = *self
|
|
.stack
|
|
.get(usize::try_from(x).map_err(|_| Error::NotEnoughStacked)?)
|
|
.ok_or(Error::NotEnoughStacked)?;
|
|
self.stack.push(y);
|
|
}
|
|
Instr::DoMath1(ubo) => {
|
|
use consts::MathUnOp as U;
|
|
let x = self.stpop()?;
|
|
self.stack.push(match ubo {
|
|
U::Not => !x,
|
|
});
|
|
}
|
|
Instr::DoMath2(mbo) => {
|
|
let b = self.stpop()?;
|
|
let a = self.stpop()?;
|
|
use consts::MathBinOp as B;
|
|
self.stack.push(match mbo {
|
|
B::Lt if a < b => 1,
|
|
B::Lt => 0,
|
|
B::Eq if a == b => 1,
|
|
B::Eq => 0,
|
|
B::And => a & b,
|
|
B::Or => a | b,
|
|
B::Xor => a ^ b,
|
|
B::Add => a.wrapping_add(b),
|
|
B::Sub => a.wrapping_sub(b),
|
|
B::Mul => a.wrapping_mul(b),
|
|
B::Div => match a.checked_div(b) {
|
|
Some(x) => x,
|
|
None => return Err(Error::DivisionByZero),
|
|
},
|
|
B::Rem => match a.checked_rem(b) {
|
|
Some(x) => x,
|
|
None => return Err(Error::DivisionByZero),
|
|
},
|
|
B::Srem => match (a as i64).checked_rem(b as i64) {
|
|
Some(x) => x as u64,
|
|
None => return Err(Error::DivisionByZero),
|
|
},
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn stack_item_size() {
|
|
assert_eq!(core::mem::size_of::<StackEntValue>(), 8);
|
|
}
|
|
|
|
proptest::proptest! {
|
|
#![proptest_config(proptest::prelude::ProptestConfig::with_cases(8192))]
|
|
|
|
#[test]
|
|
fn doesnt_crash(inp in proptest::collection::vec(0..=u8::MAX, 0..1024)) {
|
|
if fogtix_bytecode::verify(&inp[..]).is_err() {
|
|
return Ok(());
|
|
}
|
|
let mut p = Process {
|
|
stack: Vec::new(),
|
|
callstack: Vec::new(),
|
|
m: &inp[..],
|
|
instrp: 0,
|
|
};
|
|
let _ = p.run(Some(&mut 2048));
|
|
}
|
|
}
|
|
}
|