fogtix/crates/fogtix-vm/src/main.rs

301 lines
11 KiB
Rust
Raw Normal View History

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;
use std::sync::Arc;
2022-09-23 12:26:39 +00:00
mod noop;
use noop::NOOP_ORIGIN;
pub struct Module {
h: readfilez::FileHandle,
}
type InstrPtr = (Arc<Module>, usize);
pub trait Origin: Send + Sync + core::fmt::Debug {
fn call(&self, p: &Pointer, a: &Atom, stack: &mut Vec<StackEntValue>) -> InstrPtr;
fn incr_refcount(&self, p: &Pointer);
fn decr_refcount(&self, p: &Pointer);
}
#[derive(Debug)]
pub struct WrappedPointer {
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);
}
}
#[derive(Clone, Debug)]
pub enum StackEntValue {
Bytes(Vec<u8>),
Int(u64),
Atom(Atom),
Pointer(WrappedPointer),
}
struct Process {
stack: Vec<StackEntValue>,
callstack: Vec<InstrPtr>,
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..) {
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 => {}
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());
}
match self.stack.pop() {
None => {
eprintln!("ERROR: `call` invoked on empty stack @ {}", previptr);
break;
}
Some(StackEntValue::Pointer(wp)) => {
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);
}
Some(x) => {
eprintln!(
"ERROR: `call` invoked on non-pointer {:?} @ {}",
x, previptr
);
break;
}
}
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;
}
}
}
}
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;
}
};
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;
}
}
}
}
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),
BcValue::Pointer(p) => StackEntValue::Pointer(WrappedPointer {
orig: Arc::clone(&*NOOP_ORIGIN),
p,
_h: PhantomData,
}),
});
}
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() {
use clap::Arg;
2022-09-23 12:26:39 +00:00
let matches = clap::Command::new("fogtix-vm")
.version(env!("CARGO_PKG_VERSION"))
.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();
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
prc.run();
2022-09-23 12:26:39 +00:00
}