2022-09-23 21:25:47 +00:00
|
|
|
use fogtix_bytecode::{Atom, Instr, Parse, Pointer, Value as BcValue};
|
2022-09-23 14:22:21 +00:00
|
|
|
use std::sync::Arc;
|
2022-09-23 12:26:39 +00:00
|
|
|
|
2022-09-23 17:57:02 +00:00
|
|
|
mod noop;
|
|
|
|
use noop::NOOP_ORIGIN;
|
|
|
|
|
2022-09-26 01:29:25 +00:00
|
|
|
pub type Module = Arc<dyn ModuleKind>;
|
|
|
|
|
|
|
|
pub trait ModuleKind: Send + Sync {
|
|
|
|
fn as_slice(&self) -> &[u8];
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T: AsRef<[u8]> + Send + Sync + ?Sized> ModuleKind for T {
|
|
|
|
#[inline(always)]
|
|
|
|
fn as_slice(&self) -> &[u8] {
|
|
|
|
self.as_ref()
|
|
|
|
}
|
2022-09-23 14:22:21 +00:00
|
|
|
}
|
|
|
|
|
2022-09-26 01:29:25 +00:00
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct InstrPtr {
|
|
|
|
pub m: Module,
|
|
|
|
pub pos: usize,
|
|
|
|
}
|
|
|
|
|
2022-09-26 01:33:31 +00:00
|
|
|
fn next_instr(m: &Module, pos: usize) -> Option<Result<(usize, Instr<'_>), &[u8]>> {
|
2022-09-26 01:29:25 +00:00
|
|
|
m.as_slice()
|
|
|
|
.get(pos..)
|
|
|
|
.map(|nxti_arr| match Instr::parse(nxti_arr) {
|
|
|
|
Err(_) => Err(&nxti_arr[..core::cmp::min(nxti_arr.len(), 20)]),
|
|
|
|
Ok((ptr, i)) => Ok((nxti_arr.len() - ptr.len(), i)),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
impl InstrPtr {
|
|
|
|
#[inline(always)]
|
|
|
|
pub fn next_instr(&self) -> Option<Result<(usize, Instr<'_>), &[u8]>> {
|
|
|
|
next_instr(&self.m, self.pos)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_call2jump(&self) -> bool {
|
|
|
|
// tail-call optimization (would otherwise require much more opcodes)
|
|
|
|
matches!(self.next_instr(), Some(Ok((_, Instr::Return))))
|
|
|
|
}
|
|
|
|
}
|
2022-09-23 16:34:53 +00:00
|
|
|
|
2022-09-23 17:57:02 +00:00
|
|
|
pub trait Origin: Send + Sync + core::fmt::Debug {
|
2022-09-23 17:41:37 +00:00
|
|
|
fn call(&self, p: &Pointer, a: &Atom, stack: &mut Vec<StackEntValue>) -> InstrPtr;
|
2022-09-23 16:34:53 +00:00
|
|
|
}
|
|
|
|
|
2022-09-26 01:33:31 +00:00
|
|
|
#[derive(Clone, Debug)]
|
2022-09-23 17:57:02 +00:00
|
|
|
pub struct WrappedPointer {
|
2022-09-23 16:34:53 +00:00
|
|
|
orig: Arc<dyn Origin>,
|
|
|
|
p: Pointer,
|
|
|
|
}
|
|
|
|
|
2022-09-23 14:22:21 +00:00
|
|
|
#[derive(Clone, Debug)]
|
2022-09-23 17:57:02 +00:00
|
|
|
pub enum StackEntValue {
|
2022-09-23 14:22:21 +00:00
|
|
|
Bytes(Vec<u8>),
|
|
|
|
Int(u64),
|
|
|
|
Atom(Atom),
|
2022-09-23 16:34:53 +00:00
|
|
|
Pointer(WrappedPointer),
|
2022-09-23 14:22:21 +00:00
|
|
|
}
|
|
|
|
|
2022-09-26 01:29:25 +00:00
|
|
|
pub struct Process {
|
2022-09-26 03:09:14 +00:00
|
|
|
pub stack: Vec<StackEntValue>,
|
|
|
|
pub callstack: Vec<InstrPtr>,
|
|
|
|
pub instrp: InstrPtr,
|
2022-09-26 03:56:51 +00:00
|
|
|
pub fuel: Option<u64>,
|
2022-09-23 14:22:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Process {
|
2022-09-26 01:29:25 +00:00
|
|
|
pub fn new(instrp: InstrPtr) -> Self {
|
|
|
|
Self {
|
|
|
|
stack: Vec::new(),
|
|
|
|
callstack: Vec::new(),
|
|
|
|
instrp,
|
2022-09-26 03:56:51 +00:00
|
|
|
fuel: None,
|
2022-09-23 21:25:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-25 14:50:24 +00:00
|
|
|
fn verify_jumptarget_explicit(previptr: usize, jinstr: &str, jtip: &InstrPtr) -> bool {
|
2022-09-26 01:29:25 +00:00
|
|
|
if let Some(Ok((_, trgi))) = jtip.next_instr() {
|
|
|
|
if trgi != Instr::Label {
|
|
|
|
tracing::error!(
|
|
|
|
"`{}` arrived at non-jump target {:?} @ {}",
|
|
|
|
jinstr,
|
|
|
|
trgi,
|
|
|
|
previptr,
|
|
|
|
);
|
|
|
|
return false;
|
2022-09-23 21:25:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
true
|
|
|
|
}
|
|
|
|
|
2022-09-25 14:50:24 +00:00
|
|
|
fn verify_jumptarget(&self, previptr: usize, jinstr: &str) -> bool {
|
|
|
|
Self::verify_jumptarget_explicit(previptr, jinstr, &self.instrp)
|
|
|
|
}
|
|
|
|
|
2022-09-26 01:29:25 +00:00
|
|
|
pub fn run(&mut self) {
|
2022-09-23 14:22:21 +00:00
|
|
|
loop {
|
2022-09-24 23:39:47 +00:00
|
|
|
use fogtix_bytecode::consts::MathBinOp;
|
2022-09-26 01:29:25 +00:00
|
|
|
let previptr = self.instrp.pos;
|
2022-09-23 19:49:12 +00:00
|
|
|
tracing::trace!("previptr = {}", previptr);
|
2022-09-26 03:56:51 +00:00
|
|
|
if let Some(ref mut x) = &mut self.fuel {
|
|
|
|
if *x == 0 {
|
|
|
|
tracing::info!("out of fuel");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
*x -= 1;
|
|
|
|
}
|
2022-09-26 01:29:25 +00:00
|
|
|
let (nxtidelta, nxti) = match next_instr(&self.instrp.m, self.instrp.pos) {
|
2022-09-23 14:22:21 +00:00
|
|
|
None => {
|
2022-09-23 19:03:41 +00:00
|
|
|
tracing::error!(
|
|
|
|
"reached EOF of module or jumped out of bounds -> {}",
|
2022-09-23 14:22:21 +00:00
|
|
|
previptr
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
2022-09-26 01:29:25 +00:00
|
|
|
Some(Err(code)) => {
|
2022-09-23 19:03:41 +00:00
|
|
|
tracing::error!(
|
|
|
|
"reached unparsable instruction {:?} @ {}",
|
2022-09-26 01:29:25 +00:00
|
|
|
code,
|
|
|
|
self.instrp.pos
|
2022-09-23 14:22:21 +00:00
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
2022-09-26 01:29:25 +00:00
|
|
|
Some(Ok(x)) => x,
|
2022-09-23 14:22:21 +00:00
|
|
|
};
|
2022-09-26 01:29:25 +00:00
|
|
|
self.instrp.pos += nxtidelta;
|
2022-09-23 14:22:21 +00:00
|
|
|
match nxti {
|
|
|
|
Instr::Label => {}
|
2022-09-23 18:16:19 +00:00
|
|
|
Instr::CallRemote(atom, arity) => {
|
2022-09-26 01:29:25 +00:00
|
|
|
if !self.instrp.is_call2jump() {
|
2022-09-23 15:34:08 +00:00
|
|
|
self.callstack.push(self.instrp.clone());
|
|
|
|
}
|
2022-09-23 16:34:53 +00:00
|
|
|
match self.stack.pop() {
|
|
|
|
None => {
|
2022-09-23 19:03:41 +00:00
|
|
|
tracing::error!("`call-r` invoked on empty stack @ {}", previptr);
|
2022-09-23 16:34:53 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
Some(StackEntValue::Pointer(wp)) => {
|
2022-09-23 17:41:37 +00:00
|
|
|
let ssl = self.stack.len();
|
|
|
|
let mut args = self.stack.drain(ssl - usize::from(arity)..).collect();
|
|
|
|
self.instrp = wp.orig.call(&wp.p, &atom, &mut args);
|
|
|
|
self.stack.extend(args);
|
2022-09-23 16:34:53 +00:00
|
|
|
}
|
|
|
|
Some(x) => {
|
2022-09-23 19:03:41 +00:00
|
|
|
tracing::error!(
|
|
|
|
"`call-r` invoked on non-pointer {:?} @ {}",
|
|
|
|
x,
|
|
|
|
previptr
|
2022-09-23 16:34:53 +00:00
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2022-09-23 21:25:47 +00:00
|
|
|
if !self.verify_jumptarget(previptr, "call-r") {
|
|
|
|
break;
|
2022-09-23 18:16:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Instr::CallLocal(x) => {
|
2022-09-26 01:29:25 +00:00
|
|
|
if !self.instrp.is_call2jump() {
|
2022-09-23 18:16:19 +00:00
|
|
|
self.callstack.push(self.instrp.clone());
|
|
|
|
}
|
2022-09-26 01:29:25 +00:00
|
|
|
self.instrp.pos = match x.try_into() {
|
2022-09-23 18:16:19 +00:00
|
|
|
Ok(y) => y,
|
|
|
|
Err(_) => {
|
2022-09-23 19:03:41 +00:00
|
|
|
tracing::error!(
|
|
|
|
"jump to out-of-bounds address @ {} -> {}",
|
|
|
|
previptr,
|
|
|
|
x
|
2022-09-23 18:16:19 +00:00
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
2022-09-23 21:25:47 +00:00
|
|
|
if !self.verify_jumptarget(previptr, "call-l") {
|
|
|
|
break;
|
2022-09-23 16:44:43 +00:00
|
|
|
}
|
2022-09-23 14:22:21 +00:00
|
|
|
}
|
2022-09-25 14:50:24 +00:00
|
|
|
Instr::CallLDefer(x) => match x.try_into() {
|
2022-09-26 01:29:25 +00:00
|
|
|
Ok(pos) => {
|
|
|
|
let jtip = InstrPtr {
|
|
|
|
m: self.instrp.m.clone(),
|
|
|
|
pos,
|
|
|
|
};
|
2022-09-25 14:50:24 +00:00
|
|
|
if !Self::verify_jumptarget_explicit(previptr, "call-l-defer", &jtip) {
|
|
|
|
break;
|
|
|
|
}
|
2022-09-26 01:29:25 +00:00
|
|
|
if self.instrp.is_call2jump() {
|
2022-09-25 15:08:23 +00:00
|
|
|
self.instrp = jtip;
|
|
|
|
} else {
|
|
|
|
self.callstack.push(jtip);
|
|
|
|
}
|
2022-09-25 14:50:24 +00:00
|
|
|
}
|
|
|
|
Err(_) => {
|
|
|
|
tracing::error!("jump to out-of-bounds address @ {} -> {}", previptr, x);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
},
|
2022-09-23 23:49:44 +00:00
|
|
|
Instr::JumpCond(x) => {
|
|
|
|
let x: usize = match x.try_into() {
|
|
|
|
Ok(y) => y,
|
|
|
|
Err(_) => {
|
|
|
|
tracing::error!(
|
|
|
|
"jump to out-of-bounds address @ {} -> {}",
|
|
|
|
previptr,
|
|
|
|
x
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
let doit = match self.stack.pop() {
|
|
|
|
None => {
|
|
|
|
tracing::error!(
|
|
|
|
"popped empty stack during condition jump eval @ {}",
|
|
|
|
previptr
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
Some(StackEntValue::Int(i)) => i != 0,
|
2022-09-27 13:19:25 +00:00
|
|
|
Some(StackEntValue::Atom(a)) => a != Atom([0; 16]),
|
2022-09-23 23:49:44 +00:00
|
|
|
Some(z) => {
|
|
|
|
tracing::error!(
|
|
|
|
"encountered invalid condition value {:?} @ {}",
|
|
|
|
z,
|
|
|
|
previptr
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
if doit {
|
2022-09-26 01:29:25 +00:00
|
|
|
self.instrp.pos = x;
|
2022-09-23 23:49:44 +00:00
|
|
|
if !self.verify_jumptarget(previptr, "jump-cond") {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2022-09-23 14:22:21 +00:00
|
|
|
}
|
|
|
|
Instr::Return => match self.callstack.pop() {
|
|
|
|
Some(x) => self.instrp = x,
|
|
|
|
None => {
|
2022-09-23 19:49:12 +00:00
|
|
|
//tracing::error!("return called on empty callstack @ {}", previptr);
|
2022-09-23 14:22:21 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Instr::Push(v) => {
|
|
|
|
self.stack.push(match v {
|
|
|
|
BcValue::Bytes(v) => StackEntValue::Bytes(v.to_vec()),
|
|
|
|
BcValue::Int(i) => StackEntValue::Int(i),
|
|
|
|
BcValue::Atom(a) => StackEntValue::Atom(a),
|
2022-09-23 16:34:53 +00:00
|
|
|
BcValue::Pointer(p) => StackEntValue::Pointer(WrappedPointer {
|
2022-09-23 17:57:02 +00:00
|
|
|
orig: Arc::clone(&*NOOP_ORIGIN),
|
2022-09-23 16:34:53 +00:00
|
|
|
p,
|
|
|
|
}),
|
2022-09-23 14:22:21 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
Instr::Pop(_) if self.stack.is_empty() => {
|
2022-09-23 19:03:41 +00:00
|
|
|
tracing::error!("popped empty stack @ {}", previptr);
|
2022-09-23 14:22:21 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
Instr::Pop(cnt) => {
|
|
|
|
let ssl = self.stack.len() - 1;
|
2022-09-24 23:58:51 +00:00
|
|
|
let cnt = usize::from(cnt);
|
|
|
|
if cnt >= ssl {
|
|
|
|
self.stack = Vec::new();
|
|
|
|
} else {
|
|
|
|
self.stack.truncate(ssl - cnt - 1);
|
2022-09-23 14:22:21 +00:00
|
|
|
}
|
|
|
|
}
|
2022-09-24 23:58:51 +00:00
|
|
|
Instr::Dup(delta) => {
|
|
|
|
let x = match self.stack.len().checked_sub(usize::from(delta) + 1) {
|
2022-09-23 14:22:21 +00:00
|
|
|
None => {
|
2022-09-24 23:58:51 +00:00
|
|
|
tracing::error!("dup on too small stack @ {}", previptr);
|
2022-09-23 14:22:21 +00:00
|
|
|
break;
|
|
|
|
}
|
2022-09-24 23:58:51 +00:00
|
|
|
// SAFETY: the value x is always smaller than the stack height
|
|
|
|
Some(x) => self.stack[x].clone(),
|
2022-09-23 14:22:21 +00:00
|
|
|
};
|
|
|
|
self.stack.push(x);
|
|
|
|
}
|
2022-09-25 13:05:01 +00:00
|
|
|
Instr::Swap(delta) => {
|
2022-09-23 14:22:21 +00:00
|
|
|
let ssl = self.stack.len();
|
2022-09-25 13:05:01 +00:00
|
|
|
let (y, z) = match ssl.checked_sub(usize::from(delta) + 2) {
|
|
|
|
None => {
|
|
|
|
tracing::error!("swap on too small stack @ {}", previptr);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
Some(ltrg) => self.stack[ltrg..].split_at_mut(1),
|
|
|
|
};
|
2022-09-23 14:22:21 +00:00
|
|
|
core::mem::swap(&mut y[0], &mut z[0]);
|
|
|
|
}
|
2022-09-24 23:39:47 +00:00
|
|
|
Instr::ABuild => {
|
2022-09-23 14:22:21 +00:00
|
|
|
let a = self.stack.pop();
|
|
|
|
let b = self.stack.pop();
|
|
|
|
match (a, b) {
|
|
|
|
(Some(StackEntValue::Int(a)), Some(StackEntValue::Int(b))) => {
|
2022-09-27 13:19:25 +00:00
|
|
|
self.stack.push(StackEntValue::Atom((b, a).into()));
|
2022-09-23 14:22:21 +00:00
|
|
|
}
|
|
|
|
x => {
|
2022-09-23 19:03:41 +00:00
|
|
|
tracing::error!("BIF atom:build @ {} called with {:?}", previptr, x);
|
2022-09-23 14:22:21 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-09-24 23:39:47 +00:00
|
|
|
Instr::ADecon => match self.stack.pop() {
|
2022-09-27 13:19:25 +00:00
|
|
|
Some(StackEntValue::Atom(atom)) => {
|
|
|
|
let (b, a) = atom.into();
|
2022-09-23 14:22:21 +00:00
|
|
|
self.stack.push(StackEntValue::Int(b));
|
|
|
|
self.stack.push(StackEntValue::Int(a));
|
|
|
|
}
|
|
|
|
x => {
|
2022-09-24 00:07:32 +00:00
|
|
|
tracing::error!("BIF :atom:decon @ {} called with {:?}", previptr, x);
|
2022-09-23 14:22:21 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Instr::DoMath2(mbo) => {
|
|
|
|
let a = self.stack.pop();
|
|
|
|
let b = self.stack.pop();
|
|
|
|
self.stack.push(match (a, b) {
|
|
|
|
(Some(StackEntValue::Int(a)), Some(StackEntValue::Int(b))) => match mbo {
|
|
|
|
MathBinOp::NotAnd => StackEntValue::Int(!(a & b)),
|
|
|
|
MathBinOp::Add => StackEntValue::Int(a.wrapping_add(b)),
|
|
|
|
MathBinOp::Mul => {
|
|
|
|
let c = u128::from(a).wrapping_mul(u128::from(b));
|
|
|
|
StackEntValue::Atom(Atom::from(c))
|
|
|
|
}
|
|
|
|
},
|
|
|
|
(Some(StackEntValue::Atom(a)), Some(StackEntValue::Atom(b))) => {
|
|
|
|
StackEntValue::Atom(Atom::from(match mbo {
|
|
|
|
MathBinOp::NotAnd => !(u128::from(a) & u128::from(b)),
|
|
|
|
MathBinOp::Add => u128::from(a).wrapping_add(u128::from(b)),
|
|
|
|
MathBinOp::Mul => u128::from(a).wrapping_mul(u128::from(b)),
|
|
|
|
}))
|
|
|
|
}
|
2022-09-23 18:58:13 +00:00
|
|
|
(Some(StackEntValue::Bytes(mut a)), Some(StackEntValue::Bytes(mut b))) => {
|
|
|
|
StackEntValue::Bytes(match mbo {
|
2022-09-25 13:55:06 +00:00
|
|
|
MathBinOp::NotAnd if a.len() == b.len() => {
|
|
|
|
a.iter_mut()
|
|
|
|
.zip(b.into_iter())
|
|
|
|
.for_each(|(x, y)| *x = !(*x & y));
|
|
|
|
a
|
|
|
|
}
|
2022-09-23 18:58:13 +00:00
|
|
|
MathBinOp::Add => {
|
|
|
|
a.append(&mut b);
|
|
|
|
a
|
|
|
|
}
|
|
|
|
y => {
|
2022-09-23 19:03:41 +00:00
|
|
|
tracing::error!(
|
|
|
|
"BIF :math2:*({:?}) @ {} called with {:?}",
|
2022-09-23 18:58:13 +00:00
|
|
|
y,
|
|
|
|
previptr,
|
|
|
|
(
|
|
|
|
Some(StackEntValue::Bytes(a)),
|
|
|
|
Some(StackEntValue::Bytes(b)),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2022-09-23 14:22:21 +00:00
|
|
|
x => {
|
2022-09-23 19:03:41 +00:00
|
|
|
tracing::error!("BIF :math2:* @ {} called with {:?}", previptr, x);
|
2022-09-23 14:22:21 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-09-26 03:56:51 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
proptest::proptest! {
|
|
|
|
#![proptest_config(proptest::prelude::ProptestConfig::with_cases(4096))]
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn doesnt_crash(inp in proptest::collection::vec(0..=u8::MAX, 0..1024)) {
|
|
|
|
let inp: Arc<Vec<u8>> = Arc::new(inp);
|
|
|
|
let module: Module = inp;
|
|
|
|
let mut p = Process::new(InstrPtr {
|
|
|
|
m: module,
|
|
|
|
pos: 0,
|
|
|
|
});
|
|
|
|
p.fuel = Some(1024);
|
|
|
|
p.run();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|