feat: proper support for local (inside the same module) calls

This commit is contained in:
Alain Zscheile 2022-09-23 20:16:19 +02:00
parent d5148669b9
commit 993ce94fbd
5 changed files with 55 additions and 73 deletions

View file

@ -20,10 +20,11 @@ pub enum ValueType {
#[repr(u16)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, IntEnum)]
pub enum OpType {
Label = 0x6c62, /* lb */
Call = 0x636c, /* cl */
Jump = 0x6a70, /* jp */
Return = 0x7274, /* rt */
Label = 0x6c62, /* lb */
CallRemote = 0x6372, /* cr */
CallLocal = 0x636c, /* cl */
Jump = 0x6a70, /* jp */
Return = 0x7274, /* rt */
Push = 0x7073, /* ps */
Pop = 0x7071, /* pq */

View file

@ -11,7 +11,10 @@ pub enum Instr<'a> {
/// pops the descriptor table pointer from the stack,
/// then tries to call the $0 on it.
/// $1 is the amount of arguments (arity) extracted from the stack after the pointer
Call(Atom, u8),
CallRemote(Atom, u8),
/// calls the destination $0 inside of the current module
CallLocal(u64),
/// jumps to the destination $0 inside of the current module
Jump(u64),
@ -44,7 +47,8 @@ impl Instr<'_> {
pub fn typ(&self) -> OpType {
match self {
Instr::Label => OpType::Label,
Instr::Call(_, _) => OpType::Call,
Instr::CallRemote(_, _) => OpType::CallRemote,
Instr::CallLocal(_) => OpType::CallLocal,
Instr::Jump(_) => OpType::Jump,
Instr::Return => OpType::Return,
Instr::Push(_) => OpType::Push,
@ -90,11 +94,12 @@ impl<'a> crate::Parse<'a> for Instr<'a> {
}
let (inp, otyp) = OpType::parse(inp)?;
match otyp {
OpType::Call => {
OpType::CallRemote => {
let (inp, val) = Atom::parse(inp)?;
let (inp, arity) = u8::parse(inp)?;
Ok((inp, Instr::Call(val, arity)))
Ok((inp, Instr::CallRemote(val, arity)))
}
OpType::CallLocal => u64::parse(inp).map(|(inp, val)| (inp, Instr::CallLocal(val))),
OpType::Jump => u64::parse(inp).map(|(inp, val)| (inp, Instr::Jump(val))),
OpType::Push => Ok(Value::parse(inp).map(|(inp, val)| (inp, Instr::Push(val)))?),
OpType::Pop => u32::parse(inp).map(|(inp, val)| (inp, Instr::Pop(val))),
@ -115,10 +120,11 @@ impl Instr<'_> {
use int_enum::IntEnum;
writer.write_all(&self.typ().int_value().to_be_bytes())?;
match self {
Instr::Call(val, arity) => {
Instr::CallRemote(val, arity) => {
val.write_to(&mut writer)?;
writer.write_all(&[*arity])?;
}
Instr::CallLocal(val) => writer.write_all(&val.to_be_bytes())?,
Instr::Jump(val) => writer.write_all(&val.to_be_bytes())?,
Instr::Push(val) => val.write_to(writer)?,
Instr::Pop(val) => writer.write_all(&val.to_be_bytes())?,

View file

@ -11,4 +11,4 @@ pub use parse::Parse;
mod pointer;
pub use pointer::{Atom, Pointer};
mod value;
pub use value::{CallTarget, ParseError as ValueParseError, Value};
pub use value::{ParseError as ValueParseError, Value};

View file

@ -1,24 +1,8 @@
use crate::consts::{CallType, ValueType};
use crate::consts::ValueType;
use crate::{Atom, Pointer};
use core::fmt;
use int_enum::IntEnum;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum CallTarget<'a> {
Extern(&'a [u8]),
Local { offset: u64, length: u32 },
}
impl CallTarget<'_> {
#[inline]
pub fn typ(&self) -> CallType {
match self {
CallTarget::Extern(_) => CallType::Extern,
CallTarget::Local { .. } => CallType::Local,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Value<'a> {
Bytes(&'a [u8]),
@ -69,48 +53,6 @@ impl From<core::num::TryFromIntError> for ParseError {
}
}
impl<'a> crate::Parse<'a> for CallTarget<'a> {
type Err = ParseError;
fn parse(inp: &'a [u8]) -> Result<(&'a [u8], Self), ParseError> {
if inp.is_empty() {
return Err(ParseError);
}
let (vtyp, inp) = (CallType::from_int(inp[0])?, &inp[1..]);
Ok(match vtyp {
CallType::Extern => {
let (inp, data) = <&[u8]>::parse(inp)?;
(inp, CallTarget::Extern(data))
}
CallType::Local => {
let (inp, offset) = u64::parse(inp)?;
let (inp, length) = u32::parse(inp)?;
(inp, CallTarget::Local { offset, length })
}
})
}
}
#[cfg(any(test, feature = "std"))]
impl CallTarget<'_> {
pub fn write_to<W: std::io::Write>(&self, mut writer: W) -> std::io::Result<()> {
writer.write_all(&[self.typ().int_value()])?;
match self {
CallTarget::Extern(b) => {
let len: u64 = b.len().try_into().unwrap();
writer.write_all(&len.to_be_bytes())?;
writer.write_all(b)?;
}
CallTarget::Local { offset, length } => {
writer.write_all(&offset.to_be_bytes())?;
writer.write_all(&length.to_be_bytes())?;
}
}
Ok(())
}
}
impl<'a> crate::Parse<'a> for Value<'a> {
type Err = ParseError;

View file

@ -89,7 +89,7 @@ impl Process {
self.instrp.1 += nxtiptr.len() - nxti_arr.len();
match nxti {
Instr::Label => {}
Instr::Call(atom, arity) => {
Instr::CallRemote(atom, arity) => {
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) {
@ -102,7 +102,7 @@ impl Process {
}
match self.stack.pop() {
None => {
eprintln!("ERROR: `call` invoked on empty stack @ {}", previptr);
eprintln!("ERROR: `call-r` invoked on empty stack @ {}", previptr);
break;
}
Some(StackEntValue::Pointer(wp)) => {
@ -113,7 +113,7 @@ impl Process {
}
Some(x) => {
eprintln!(
"ERROR: `call` invoked on non-pointer {:?} @ {}",
"ERROR: `call-r` invoked on non-pointer {:?} @ {}",
x, previptr
);
break;
@ -123,7 +123,40 @@ impl Process {
if let Ok((_, trgi)) = Instr::parse(n2xti_arr) {
if trgi != Instr::Label {
eprintln!(
"ERROR: `call` arrived at non-jump target {:?} @ {}",
"ERROR: `call-r` arrived at non-jump target {:?} @ {}",
trgi, previptr
);
break;
}
}
}
}
Instr::CallLocal(x) => {
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());
}
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: `call-l` arrived at non-jump target {:?} @ {}",
trgi, previptr
);
break;