2022-09-23 14:22:21 +00:00
|
|
|
use fogtix_bytecode::{Atom, Parse, Pointer, Value as BcValue};
|
|
|
|
use std::marker::PhantomData;
|
2022-09-23 12:26:39 +00:00
|
|
|
use std::path::PathBuf;
|
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;
|
|
|
|
|
|
|
|
pub struct Module {
|
2022-09-23 14:22:21 +00:00
|
|
|
h: readfilez::FileHandle,
|
|
|
|
}
|
|
|
|
|
2022-09-23 16:34:53 +00:00
|
|
|
type InstrPtr = (Arc<Module>, usize);
|
|
|
|
|
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
|
|
|
fn incr_refcount(&self, p: &Pointer);
|
|
|
|
fn decr_refcount(&self, p: &Pointer);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(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,
|
|
|
|
_h: PhantomData<*const StackEntValue>,
|
|
|
|
}
|
|
|
|
|
|
|
|
unsafe impl Send for WrappedPointer {}
|
|
|
|
unsafe impl Sync for WrappedPointer {}
|
|
|
|
|
|
|
|
impl Clone for WrappedPointer {
|
|
|
|
fn clone(&self) -> Self {
|
|
|
|
self.orig.incr_refcount(&self.p);
|
|
|
|
Self {
|
|
|
|
orig: Arc::clone(&self.orig),
|
|
|
|
p: self.p,
|
|
|
|
_h: PhantomData,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for WrappedPointer {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
self.orig.decr_refcount(&self.p);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
struct Process {
|
|
|
|
stack: Vec<StackEntValue>,
|
2022-09-23 16:34:53 +00:00
|
|
|
callstack: Vec<InstrPtr>,
|
2022-09-23 14:22:21 +00:00
|
|
|
instrp: (Arc<Module>, usize),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Process {
|
|
|
|
fn run(&mut self) {
|
|
|
|
loop {
|
|
|
|
use fogtix_bytecode::consts::{AtomOp, MathBinOp};
|
|
|
|
use fogtix_bytecode::Instr;
|
|
|
|
let previptr = self.instrp.1;
|
2022-09-23 15:34:08 +00:00
|
|
|
let nxti_arr = match self.instrp.0.h.get(previptr..) {
|
2022-09-23 14:22:21 +00:00
|
|
|
None => {
|
|
|
|
eprintln!(
|
|
|
|
"ERROR: reached EOF of module or jumped out of bounds -> {}",
|
|
|
|
previptr
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
Some(x) => x,
|
|
|
|
};
|
|
|
|
let (nxtiptr, nxti) = match Instr::parse(nxti_arr) {
|
|
|
|
Err(_) => {
|
|
|
|
eprintln!(
|
|
|
|
"ERROR: reached unparsable instruction {:?} @ {}",
|
|
|
|
&nxti_arr[..core::cmp::min(nxti_arr.len(), 20)],
|
|
|
|
self.instrp.1
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
Ok(x) => x,
|
|
|
|
};
|
|
|
|
self.instrp.1 += nxtiptr.len() - nxti_arr.len();
|
|
|
|
match nxti {
|
|
|
|
Instr::Label => {}
|
2022-09-23 17:41:37 +00:00
|
|
|
Instr::Call(atom, arity) => {
|
2022-09-23 15:34:08 +00:00
|
|
|
let mut call2jump = false;
|
|
|
|
if let Some(n2xti_arr) = self.instrp.0.h.get(self.instrp.1..) {
|
|
|
|
if let Ok((_, Instr::Return)) = Instr::parse(n2xti_arr) {
|
|
|
|
// tail-call optimization (would otherwise require another opcode)
|
|
|
|
call2jump = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !call2jump {
|
|
|
|
self.callstack.push(self.instrp.clone());
|
|
|
|
}
|
2022-09-23 16:34:53 +00:00
|
|
|
match self.stack.pop() {
|
|
|
|
None => {
|
|
|
|
eprintln!("ERROR: `call` invoked on empty stack @ {}", previptr);
|
|
|
|
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) => {
|
|
|
|
eprintln!(
|
|
|
|
"ERROR: `call` invoked on non-pointer {:?} @ {}",
|
|
|
|
x, previptr
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2022-09-23 16:44:43 +00:00
|
|
|
if let Some(n2xti_arr) = self.instrp.0.h.get(self.instrp.1..) {
|
|
|
|
if let Ok((_, trgi)) = Instr::parse(n2xti_arr) {
|
|
|
|
if trgi != Instr::Label {
|
|
|
|
eprintln!(
|
|
|
|
"ERROR: `call` arrived at non-jump target {:?} @ {}",
|
|
|
|
trgi, previptr
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-09-23 14:22:21 +00:00
|
|
|
}
|
|
|
|
Instr::Jump(x) => {
|
|
|
|
self.instrp.1 = match x.try_into() {
|
|
|
|
Ok(y) => y,
|
|
|
|
Err(_) => {
|
|
|
|
eprintln!(
|
|
|
|
"ERROR: jump to out-of-bounds address @ {} -> {}",
|
|
|
|
previptr, x
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
2022-09-23 16:44:43 +00:00
|
|
|
};
|
|
|
|
if let Some(n2xti_arr) = self.instrp.0.h.get(self.instrp.1..) {
|
|
|
|
if let Ok((_, trgi)) = Instr::parse(n2xti_arr) {
|
|
|
|
if trgi != Instr::Label {
|
|
|
|
eprintln!(
|
|
|
|
"ERROR: jump arrived at non-jump target {:?} @ {}",
|
|
|
|
trgi, previptr
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2022-09-23 14:22:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Instr::Return => match self.callstack.pop() {
|
|
|
|
Some(x) => self.instrp = x,
|
|
|
|
None => {
|
|
|
|
eprintln!("ERROR: return called on empty callstack @ {}", previptr);
|
|
|
|
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,
|
|
|
|
_h: PhantomData,
|
|
|
|
}),
|
2022-09-23 14:22:21 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
Instr::Pop(_) if self.stack.is_empty() => {
|
|
|
|
eprintln!("ERROR: popped empty stack @ {}", previptr);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
Instr::Pop(cnt) => {
|
|
|
|
let ssl = self.stack.len() - 1;
|
|
|
|
match usize::try_from(cnt) {
|
|
|
|
Ok(cnt) => {
|
|
|
|
if cnt >= ssl {
|
|
|
|
self.stack = Vec::new();
|
|
|
|
} else {
|
|
|
|
self.stack.truncate(ssl - cnt - 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(_) => self.stack = Vec::new(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Instr::Dup => {
|
|
|
|
let x = match self.stack.last() {
|
|
|
|
None => {
|
|
|
|
eprintln!("ERROR: dup on empty stack @ {}", previptr);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
Some(x) => x.clone(),
|
|
|
|
};
|
|
|
|
self.stack.push(x);
|
|
|
|
}
|
|
|
|
Instr::Swap => {
|
|
|
|
let ssl = self.stack.len();
|
|
|
|
if ssl < 2 {
|
|
|
|
eprintln!("ERROR: swap on empty stack @ {}", previptr);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
let x = &mut self.stack[ssl - 2..ssl];
|
|
|
|
let (y, z) = x.split_at_mut(1);
|
|
|
|
core::mem::swap(&mut y[0], &mut z[0]);
|
|
|
|
}
|
|
|
|
Instr::OnAtom(AtomOp::Build) => {
|
|
|
|
let a = self.stack.pop();
|
|
|
|
let b = self.stack.pop();
|
|
|
|
match (a, b) {
|
|
|
|
(Some(StackEntValue::Int(a)), Some(StackEntValue::Int(b))) => {
|
|
|
|
self.stack.push(StackEntValue::Atom(Atom(b, a)));
|
|
|
|
}
|
|
|
|
x => {
|
|
|
|
eprintln!("ERROR: BIF atom:build @ {} called with {:?}", previptr, x);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Instr::OnAtom(AtomOp::Upper) => match self.stack.pop() {
|
|
|
|
Some(StackEntValue::Atom(Atom(b, _))) => {
|
|
|
|
self.stack.push(StackEntValue::Int(b));
|
|
|
|
}
|
|
|
|
x => {
|
|
|
|
eprintln!("ERROR: BIF :atom:upper @ {} called with {:?}", previptr, x);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Instr::OnAtom(AtomOp::Lower) => match self.stack.pop() {
|
|
|
|
Some(StackEntValue::Atom(Atom(_, a))) => {
|
|
|
|
self.stack.push(StackEntValue::Int(a));
|
|
|
|
}
|
|
|
|
x => {
|
|
|
|
eprintln!("ERROR: BIF :atom:lower @ {} called with {:?}", previptr, x);
|
|
|
|
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)),
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
x => {
|
|
|
|
eprintln!("ERROR: BIF :math2:* @ {} called with {:?}", previptr, x);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-09-23 12:26:39 +00:00
|
|
|
|
|
|
|
fn main() {
|
2022-09-23 14:22:21 +00:00
|
|
|
use clap::Arg;
|
2022-09-23 12:26:39 +00:00
|
|
|
let matches = clap::Command::new("fogtix-vm")
|
|
|
|
.version(env!("CARGO_PKG_VERSION"))
|
2022-09-23 14:22:21 +00:00
|
|
|
.arg(
|
|
|
|
Arg::new("main")
|
|
|
|
.short('m')
|
|
|
|
.help("the module whose entry point should be used")
|
|
|
|
.required(true),
|
2022-09-23 12:26:39 +00:00
|
|
|
)
|
|
|
|
.get_matches();
|
|
|
|
|
2022-09-23 14:22:21 +00:00
|
|
|
let main_mod_path = matches.get_one::<PathBuf>("main").unwrap();
|
|
|
|
|
|
|
|
let main_mod = Arc::new(Module {
|
|
|
|
h: readfilez::read_from_file(std::fs::File::open(&main_mod_path))
|
|
|
|
.expect("unable to open/load main module"),
|
|
|
|
});
|
|
|
|
|
|
|
|
let mut prc = Process {
|
|
|
|
stack: Vec::new(),
|
|
|
|
callstack: Vec::new(),
|
|
|
|
instrp: (main_mod, 0),
|
|
|
|
};
|
2022-09-23 12:26:39 +00:00
|
|
|
|
2022-09-23 14:22:21 +00:00
|
|
|
prc.run();
|
2022-09-23 12:26:39 +00:00
|
|
|
}
|