feat(vm): callstack doesn't really need to always store the module pointer for each callframe

This commit is contained in:
Alain Zscheile 2022-10-15 17:09:36 +02:00
parent e101c9fd9f
commit 25ae04cb37
2 changed files with 39 additions and 79 deletions

View file

@ -18,20 +18,18 @@ fn main() {
let main_mod_path = matches.get_one::<String>("main").unwrap();
let main_mod = Arc::new(
readfilez::read_from_file(std::fs::File::open(&main_mod_path))
readfilez::read_from_file(std::fs::File::open(main_mod_path))
.expect("unable to open/load main module"),
);
let mut p = fogtix_vm::Process {
stack: Vec::new(),
callstack: Vec::new(),
instrp: fogtix_vm::InstrPtr {
m: main_mod,
pos: 0,
},
m: main_mod,
instrp: 0,
};
if let Err(e) = p.run(None) {
eprintln!("ERROR@{}: {}", p.instrp.pos, e);
eprintln!("ERROR@{}: {}", p.instrp, e);
}
}

View file

@ -20,27 +20,7 @@ impl<T: AsRef<[u8]> + Send + Sync + ?Sized> ModuleKind for T {
}
}
#[derive(Clone)]
pub struct InstrPtr {
pub m: Module,
pub pos: usize,
}
impl fmt::Display for InstrPtr {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "@{:p}:{:x}", self.m, self.pos)
}
}
impl fmt::Debug for InstrPtr {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
<Self as fmt::Display>::fmt(self, f)
}
}
fn next_instr(m: &Module, pos: usize) -> Option<Result<(usize, Instr), &[u8]>> {
fn next_instr(m: &dyn ModuleKind, pos: usize) -> Option<Result<(usize, Instr), &[u8]>> {
m.as_slice()
.get(pos..)
.map(|nxti_arr| match Instr::parse(nxti_arr) {
@ -49,16 +29,9 @@ fn next_instr(m: &Module, pos: usize) -> Option<Result<(usize, Instr), &[u8]>> {
})
}
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))))
}
fn is_call2jump(m: &dyn ModuleKind, pos: usize) -> bool {
// tail-call optimization (would otherwise require much more opcodes)
matches!(next_instr(m, pos), Some(Ok((_, Instr::Return))))
}
pub type StackEntValue = u64;
@ -67,8 +40,8 @@ pub type StackEntValue = u64;
pub enum Error {
InvalidJumpTarget {
invoked_by: &'static str,
from: InstrPtr,
to: InstrPtr,
from: usize,
to: usize,
},
InstrpOutOfBounds,
UnparsableInstruction(Vec<u8>),
@ -104,31 +77,33 @@ impl fmt::Display for Error {
pub struct Process {
pub stack: Vec<StackEntValue>,
pub callstack: Vec<InstrPtr>,
pub instrp: InstrPtr,
pub m: Module,
pub callstack: Vec<usize>,
pub instrp: usize,
}
fn verify_jumptarget_explicit(
jinstr: &'static str,
from: &InstrPtr,
to: &InstrPtr,
m: &dyn ModuleKind,
from: usize,
to: usize,
) -> Result<(), Error> {
if to.pos == 0 {
if to == 0 {
Ok(())
} else if let Some(Ok((_, Instr::Label))) = to.next_instr() {
} else if let Some(Ok((_, Instr::Label))) = next_instr(m, to) {
Ok(())
} else {
Err(Error::InvalidJumpTarget {
invoked_by: jinstr,
from: from.clone(),
to: to.clone(),
from,
to,
})
}
}
impl Process {
fn verify_jumptarget(&self, jinstr: &'static str, from: &InstrPtr) -> Result<(), Error> {
verify_jumptarget_explicit(jinstr, from, &self.instrp)
fn verify_jumptarget(&self, jinstr: &'static str, from: usize) -> Result<(), Error> {
verify_jumptarget_explicit(jinstr, &*self.m, from, self.instrp)
}
fn stpop(&mut self) -> Result<StackEntValue, Error> {
@ -137,7 +112,7 @@ impl Process {
pub fn run(&mut self, mut fuel: Option<&mut u64>) -> Result<(), Error> {
loop {
let previptr = self.instrp.pos;
let previptr = self.instrp;
tracing::trace!("previptr = {}", previptr);
if let Some(ref mut x) = fuel {
if **x == 0 {
@ -145,51 +120,43 @@ impl Process {
}
**x -= 1;
}
let (nxtidelta, nxti) = match next_instr(&self.instrp.m, self.instrp.pos) {
let (nxtidelta, nxti) = match next_instr(&*self.m, self.instrp) {
None => return Err(Error::InstrpOutOfBounds),
Some(Err(code)) => return Err(Error::UnparsableInstruction(code.to_vec())),
Some(Ok(x)) => x,
};
self.instrp.pos += nxtidelta;
self.instrp += nxtidelta;
match nxti {
Instr::Label => {}
Instr::CallRemote => {
if !self.instrp.is_call2jump() {
self.callstack.push(self.instrp.clone());
if !is_call2jump(&*self.m, self.instrp) {
self.callstack.push(self.instrp);
}
return Err(Error::RemoteCall(self.stpop()?));
}
Instr::CallLocal(x) => {
if !self.instrp.is_call2jump() {
if !is_call2jump(&*self.m, self.instrp) {
self.callstack.push(self.instrp.clone());
}
self.instrp.pos = match x.try_into() {
self.instrp = match x.try_into() {
Ok(y) => y,
Err(_) => return Err(Error::InstrpOutOfBounds),
};
self.verify_jumptarget(
"call-l",
&InstrPtr {
m: self.instrp.m.clone(),
pos: previptr,
},
previptr,
)?;
}
Instr::CallLDefer(x) => match x.try_into() {
Ok(pos) => {
let jtip = InstrPtr {
m: self.instrp.m.clone(),
pos,
};
let jtip = pos;
verify_jumptarget_explicit(
"call-l-defer",
&InstrPtr {
m: self.instrp.m.clone(),
pos: previptr,
},
&jtip,
&*self.m,
previptr,
jtip,
)?;
if self.instrp.is_call2jump() {
if is_call2jump(&*self.m, self.instrp) {
self.instrp = jtip;
} else {
self.callstack.push(jtip);
@ -203,13 +170,10 @@ impl Process {
Err(_) => return Err(Error::InstrpOutOfBounds),
};
if self.stpop()? != 0 {
self.instrp.pos = x;
self.instrp = x;
self.verify_jumptarget(
"jump-cond",
&InstrPtr {
m: self.instrp.m.clone(),
pos: previptr,
},
previptr,
)?;
}
}
@ -312,10 +276,8 @@ mod tests {
let mut p = Process {
stack: Vec::new(),
callstack: Vec::new(),
instrp: InstrPtr {
m: module,
pos: 0,
},
m: module,
instrp: 0,
};
let _ = p.run(Some(&mut 2048));
}