feat+rf(vm): replace WrappedPointer with Pointer, reducing stack item size from 48 bytes to 32

This commit is contained in:
Alain Zscheile 2022-09-27 16:39:28 +02:00
parent 6256336a5c
commit 911d662634
5 changed files with 97 additions and 86 deletions

View file

@ -44,7 +44,10 @@ impl From<Atom> for (u64, u64) {
#[inline]
fn from(Atom(x): Atom) -> (u64, u64) {
let (a, b) = x.split_at(8);
(u64::from_be_bytes(a.try_into().unwrap()), u64::from_be_bytes(b.try_into().unwrap()))
(
u64::from_be_bytes(a.try_into().unwrap()),
u64::from_be_bytes(b.try_into().unwrap()),
)
}
}

View file

@ -22,9 +22,14 @@ fn main() {
.expect("unable to open/load main module"),
);
fogtix_vm::Process::new(fogtix_vm::InstrPtr {
m: main_mod,
pos: 0,
})
.run();
fogtix_vm::Process {
korigs: fogtix_vm::make_default_origins(),
stack: Vec::new(),
callstack: Vec::new(),
instrp: fogtix_vm::InstrPtr {
m: main_mod,
pos: 0,
},
}
.run(None);
}

View file

@ -12,10 +12,8 @@ tracing = "0.1"
path = "../fogtix-bytecode"
features = ["std"]
[dependencies.once_cell]
version = "1.15"
[dev-dependencies]
once_cell = "1.15"
[dev-dependencies.proptest]
version = "1.0"

View file

@ -1,8 +1,5 @@
use fogtix_bytecode::{Atom, Instr, Parse, Pointer, Value as BcValue};
use std::sync::Arc;
mod noop;
use noop::NOOP_ORIGIN;
use std::sync::{Arc, RwLock};
pub type Module = Arc<dyn ModuleKind>;
@ -48,10 +45,18 @@ pub trait Origin: Send + Sync + core::fmt::Debug {
fn call(&self, p: &Pointer, a: &Atom, stack: &mut Vec<StackEntValue>) -> InstrPtr;
}
#[derive(Clone, Debug)]
pub struct WrappedPointer {
orig: Arc<dyn Origin>,
p: Pointer,
pub type KnownOrigins = Arc<[RwLock<Option<Box<dyn Origin>>>; 0x10000]>;
fn make_large_arc_slice<const N: usize, T, F: Fn() -> T>(f: F) -> Arc<[T; N]> {
// see also: https://stackoverflow.com/a/68122278
// license: CC BY-SA 4.0; author: "Johannes Maria Frank"
let bxs = core::iter::repeat_with(f).take(N).collect::<Arc<[T]>>();
let ptr = Arc::into_raw(bxs) as *mut [T; N];
unsafe { Arc::from_raw(ptr) }
}
pub fn make_default_origins() -> KnownOrigins {
make_large_arc_slice::<0x10000, RwLock<Option<Box<dyn Origin>>>, _>(|| RwLock::new(None))
}
#[derive(Clone, Debug)]
@ -59,56 +64,47 @@ pub enum StackEntValue {
Bytes(Vec<u8>),
Int(u64),
Atom(Atom),
Pointer(WrappedPointer),
Pointer(Pointer),
}
pub struct Process {
pub korigs: KnownOrigins,
pub stack: Vec<StackEntValue>,
pub callstack: Vec<InstrPtr>,
pub instrp: InstrPtr,
pub fuel: Option<u64>,
}
fn verify_jumptarget_explicit(previptr: usize, jinstr: &str, jtip: &InstrPtr) -> bool {
if let Some(Ok((_, trgi))) = jtip.next_instr() {
if trgi != Instr::Label {
tracing::error!(
"`{}` arrived at non-jump target {:?} @ {}",
jinstr,
trgi,
previptr,
);
return false;
}
}
true
}
impl Process {
pub fn new(instrp: InstrPtr) -> Self {
Self {
stack: Vec::new(),
callstack: Vec::new(),
instrp,
fuel: None,
}
}
fn verify_jumptarget_explicit(previptr: usize, jinstr: &str, jtip: &InstrPtr) -> bool {
if let Some(Ok((_, trgi))) = jtip.next_instr() {
if trgi != Instr::Label {
tracing::error!(
"`{}` arrived at non-jump target {:?} @ {}",
jinstr,
trgi,
previptr,
);
return false;
}
}
true
}
fn verify_jumptarget(&self, previptr: usize, jinstr: &str) -> bool {
Self::verify_jumptarget_explicit(previptr, jinstr, &self.instrp)
verify_jumptarget_explicit(previptr, jinstr, &self.instrp)
}
pub fn run(&mut self) {
pub fn run(&mut self, mut fuel: Option<&mut u64>) {
loop {
use fogtix_bytecode::consts::MathBinOp;
let previptr = self.instrp.pos;
tracing::trace!("previptr = {}", previptr);
if let Some(ref mut x) = &mut self.fuel {
if *x == 0 {
if let Some(ref mut x) = fuel {
if **x == 0 {
tracing::info!("out of fuel");
break;
}
*x -= 1;
**x -= 1;
}
let (nxtidelta, nxti) = match next_instr(&self.instrp.m, self.instrp.pos) {
None => {
@ -143,8 +139,32 @@ impl Process {
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);
let origin_id = usize::from(wp.origin());
let origin = self.korigs[origin_id].read();
match origin {
Ok(origin) => match origin.as_ref() {
Some(origin) => {
self.instrp = origin.call(&wp, &atom, &mut args);
self.stack.extend(args);
}
None => {
tracing::error!(
"`call-r` invoked on invalid origin {:x} @ {}",
origin_id,
previptr
);
break;
}
},
Err(_) => {
tracing::error!(
"`call-r` invoked on poisoned origin {:x} @ {}",
origin_id,
previptr
);
break;
}
}
}
Some(x) => {
tracing::error!(
@ -184,7 +204,7 @@ impl Process {
m: self.instrp.m.clone(),
pos,
};
if !Self::verify_jumptarget_explicit(previptr, "call-l-defer", &jtip) {
if !verify_jumptarget_explicit(previptr, "call-l-defer", &jtip) {
break;
}
if self.instrp.is_call2jump() {
@ -248,10 +268,7 @@ impl Process {
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,
}),
BcValue::Pointer(p) => StackEntValue::Pointer(p),
});
}
Instr::Pop(_) if self.stack.is_empty() => {
@ -372,19 +389,32 @@ impl Process {
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stack_item_size() {
assert_eq!(core::mem::size_of::<StackEntValue>(), 32);
}
proptest::proptest! {
#![proptest_config(proptest::prelude::ProptestConfig::with_cases(4096))]
#[test]
fn doesnt_crash(inp in proptest::collection::vec(0..=u8::MAX, 0..1024)) {
use once_cell::sync::Lazy;
// we use a static here to avoid allocating the very large array on every iteration
static KORIGS: Lazy<KnownOrigins> = Lazy::new(|| make_default_origins());
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();
let mut p = Process {
korigs: Arc::clone(&*KORIGS),
stack: Vec::new(),
callstack: Vec::new(),
instrp: InstrPtr {
m: module,
pos: 0,
},
};
p.run(Some(&mut 1024));
}
}
}

View file

@ -1,25 +0,0 @@
use fogtix_bytecode::{Atom, Pointer};
use once_cell::sync::Lazy;
use std::sync::Arc;
use crate::{InstrPtr, Module, Origin, StackEntValue};
pub static NOOP_MODULE: Lazy<Module> = Lazy::new(|| Arc::new(b"LR".as_slice()));
#[derive(Debug)]
struct NoopOrigin;
impl Origin for NoopOrigin {
fn call(&self, p: &Pointer, a: &Atom, _stack: &mut Vec<StackEntValue>) -> InstrPtr {
tracing::error!(
"tried to invoke pointer {:?}({:?}) without valid context",
p,
a
);
InstrPtr {
m: Arc::clone(&NOOP_MODULE),
pos: 0,
}
}
}
pub static NOOP_ORIGIN: Lazy<Arc<dyn Origin>> = Lazy::new(|| Arc::new(NoopOrigin));