feat: more and better math operators

This commit is contained in:
Alain Zscheile 2022-09-28 01:48:32 +02:00
parent cdebdd2ce9
commit 7602e9f7b9
3 changed files with 223 additions and 129 deletions

View file

@ -9,6 +9,7 @@ pub enum ValueType {
Int_ = 0x49, /* I */
Pointer = 0x50, /* P */
}
impl Sealed for ValueType {}
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, IntEnum)]
@ -20,27 +21,45 @@ pub enum OpType {
CallLDefer = 0x64, /* d */
JumpCond = 0x6a, /* j */
Return = 0x52, /* R */
// stack operations
Push = 0x50, /* P */
Pop = 0x51, /* Q */
Dup = 0x44, /* D */
Swap = 0x53, /* S */
// atom operations
ABuild = 0x2e, /* . */
ADecon = 0x2a, /* * */
// extensions
DoMath1 = 0x6d, /* m */
DoMath2 = 0x4d, /* M */
}
impl Sealed for OpType {}
// u16 because we assume we may add many additional math operations in the future
#[repr(u16)]
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, IntEnum)]
pub enum MathUnOp {
// bit operations
Not = 0x7e, /* ~ */
}
impl Sealed for MathUnOp {}
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, IntEnum)]
pub enum MathBinOp {
NotAnd = 0x7e26, /* ~& */
Add = 0x202b, /* + */
Mul = 0x202a, /* * */
}
Concat = 0x2e, /* . */
impl Sealed for ValueType {}
impl Sealed for OpType {}
// bit operations
And = 0x26, /* & */
Or = 0x7c, /* | */
Xor = 0x5e, /* ^ */
// arithmetic operations
Rem = 0x25, /* % */
Mul = 0x2a, /* * */
Add = 0x2b, /* + */
Sub = 0x2d, /* - */
Div = 0x2f, /* / */
}
impl Sealed for MathBinOp {}

View file

@ -1,5 +1,5 @@
use crate::consts::{MathBinOp, OpType};
use crate::{Atom, Value};
use crate::consts::{MathBinOp, MathUnOp, OpType};
use crate::Value;
use core::fmt;
#[derive(Clone, Debug, PartialEq, Eq)]
@ -9,10 +9,11 @@ pub enum Instr<'a> {
Label,
/// 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
CallRemote(Atom, u8),
/// then tries to call it with the top $0 stack elements as arguments.
/// thus all-in-all this pops `1+$0` values from the stack, then
/// places an arbitrary amount of values on the stack,
/// followed by the `Int(return value count)`
CallRemote,
/// calls the destination $0 inside of the current module
CallLocal(u64),
@ -45,10 +46,10 @@ pub enum Instr<'a> {
Swap(u16),
/// basic atom operations
ABuild,
ADecon,
/// basic math operations (on integers and atoms)
DoMath1(MathUnOp),
DoMath2(MathBinOp),
}
@ -57,7 +58,7 @@ impl Instr<'_> {
pub fn typ(&self) -> OpType {
match self {
Instr::Label => OpType::Label,
Instr::CallRemote(_, _) => OpType::CallRemote,
Instr::CallRemote => OpType::CallRemote,
Instr::CallLocal(_) => OpType::CallLocal,
Instr::CallLDefer(_) => OpType::CallLDefer,
Instr::JumpCond(_) => OpType::JumpCond,
@ -66,8 +67,8 @@ impl Instr<'_> {
Instr::Pop(_) => OpType::Pop,
Instr::Dup(_) => OpType::Dup,
Instr::Swap(_) => OpType::Swap,
Instr::ABuild => OpType::ABuild,
Instr::ADecon => OpType::ADecon,
Instr::DoMath1(_) => OpType::DoMath1,
Instr::DoMath2(_) => OpType::DoMath2,
}
}
@ -106,11 +107,6 @@ impl<'a> crate::Parse<'a> for Instr<'a> {
}
let (inp, otyp) = OpType::parse(inp)?;
match otyp {
OpType::CallRemote => {
let (inp, val) = Atom::parse(inp)?;
let (inp, arity) = u8::parse(inp)?;
Ok((inp, Instr::CallRemote(val, arity)))
}
OpType::CallLocal => u64::parse(inp).map(|(inp, val)| (inp, Instr::CallLocal(val))),
OpType::CallLDefer => u64::parse(inp).map(|(inp, val)| (inp, Instr::CallLDefer(val))),
OpType::JumpCond => u64::parse(inp).map(|(inp, val)| (inp, Instr::JumpCond(val))),
@ -118,10 +114,11 @@ impl<'a> crate::Parse<'a> for Instr<'a> {
OpType::Pop => u16::parse(inp).map(|(inp, val)| (inp, Instr::Pop(val))),
OpType::Dup => u16::parse(inp).map(|(inp, val)| (inp, Instr::Dup(val))),
OpType::Swap => u16::parse(inp).map(|(inp, val)| (inp, Instr::Swap(val))),
OpType::DoMath1 => MathUnOp::parse(inp).map(|(inp, val)| (inp, Instr::DoMath1(val))),
OpType::DoMath2 => MathBinOp::parse(inp).map(|(inp, val)| (inp, Instr::DoMath2(val))),
OpType::Label => Ok((inp, Instr::Label)),
OpType::CallRemote => Ok((inp, Instr::CallRemote)),
OpType::Return => Ok((inp, Instr::Return)),
OpType::ABuild => Ok((inp, Instr::ABuild)),
OpType::ADecon => Ok((inp, Instr::ADecon)),
}
.map_err(|()| ParseError)
@ -134,21 +131,17 @@ impl Instr<'_> {
use int_enum::IntEnum;
writer.write_all(&self.typ().int_value().to_be_bytes())?;
match self {
Instr::CallRemote(val, arity) => {
val.write_to(&mut writer)?;
writer.write_all(&[*arity])?;
}
Instr::CallLocal(val) => writer.write_all(&val.to_be_bytes())?,
Instr::CallLDefer(val) => writer.write_all(&val.to_be_bytes())?,
Instr::JumpCond(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())?,
Instr::Dup(val) => writer.write_all(&val.to_be_bytes())?,
Instr::Swap(val) => writer.write_all(&val.to_be_bytes())?,
Instr::DoMath2(val) => writer.write_all(&val.int_value().to_be_bytes())?,
Instr::Label | Instr::Return | Instr::ABuild | Instr::ADecon => {}
Instr::CallLocal(val) => writer.write_all(&val.to_be_bytes()),
Instr::CallLDefer(val) => writer.write_all(&val.to_be_bytes()),
Instr::JumpCond(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()),
Instr::Dup(val) => writer.write_all(&val.to_be_bytes()),
Instr::Swap(val) => writer.write_all(&val.to_be_bytes()),
Instr::DoMath1(val) => writer.write_all(&val.int_value().to_be_bytes()),
Instr::DoMath2(val) => writer.write_all(&val.int_value().to_be_bytes()),
Instr::Label | Instr::CallRemote | Instr::Return | Instr::ADecon => Ok(()),
}
Ok(())
}
}

View file

@ -1,5 +1,5 @@
use async_trait::async_trait;
use fogtix_bytecode::{Atom, Instr, Parse, Pointer, Value as BcValue};
use fogtix_bytecode::{consts, Atom, Instr, Parse, Pointer, Value as BcValue};
use std::sync::{Arc, RwLock};
pub type Module = Arc<dyn ModuleKind>;
@ -44,7 +44,7 @@ impl InstrPtr {
#[async_trait]
pub trait Origin: Send + Sync + core::fmt::Debug {
async fn call(&self, p: &Pointer, a: &Atom, stack: &mut Vec<StackEntValue>) -> InstrPtr;
async fn call(&self, p: &Pointer, stack: &mut Vec<StackEntValue>) -> InstrPtr;
}
pub type KnownOrigins = Arc<[RwLock<Option<Box<dyn Origin>>>; 0x10000]>;
@ -91,6 +91,9 @@ pub enum Error {
#[error("operand from stack doesn't have correct data type: expected={expected}, got={got}")]
StackedInvalidType { expected: &'static str, got: String },
#[error("tried to divide by zero")]
DivisionByZero,
}
pub struct Process {
@ -117,14 +120,43 @@ fn verify_jumptarget_explicit(
Ok(())
}
fn u128_binmath(a: u128, b: u128, mbo: consts::MathBinOp) -> Result<u128, Error> {
use consts::MathBinOp as B;
Ok(match mbo {
B::Concat => {
return Err(Error::StackedInvalidType {
expected: "bytes,bytes",
got: "[int|atom, int|atom]".to_string(),
})
}
B::And => a & b,
B::Or => a | b,
B::Xor => a ^ b,
B::Add => a.wrapping_add(b),
B::Sub => a.wrapping_sub(b),
B::Mul => a.wrapping_mul(b),
B::Div => match a.checked_div(b) {
Some(x) => x,
None => return Err(Error::DivisionByZero),
},
B::Rem => match a.checked_rem(b) {
Some(x) => x,
None => return Err(Error::DivisionByZero),
},
})
}
impl Process {
fn verify_jumptarget(&self, previptr: usize, jinstr: &'static str) -> Result<(), Error> {
verify_jumptarget_explicit(previptr, jinstr, &self.instrp)
}
fn stpop(&mut self) -> Result<StackEntValue, Error> {
self.stack.pop().ok_or(Error::NotEnoughStacked)
}
pub async fn run(&mut self, mut fuel: Option<&mut u64>) -> Result<(), Error> {
loop {
use fogtix_bytecode::consts::MathBinOp;
let previptr = self.instrp.pos;
tracing::trace!("previptr = {}", previptr);
if let Some(ref mut x) = fuel {
@ -141,39 +173,50 @@ impl Process {
self.instrp.pos += nxtidelta;
match nxti {
Instr::Label => {}
Instr::CallRemote(atom, arity) => {
Instr::CallRemote => {
if !self.instrp.is_call2jump() {
self.callstack.push(self.instrp.clone());
}
self.verify_jumptarget(previptr, "call-r")?;
match self.stack.pop() {
None => {
return Err(Error::NotEnoughStacked);
}
Some(StackEntValue::Pointer(mut wp)) => {
let ssl = self.stack.len();
let mut args = self.stack.drain(ssl - usize::from(arity)..).collect();
let origin_id = wp.origin();
let origin = self.korigs[usize::from(origin_id)].read();
match origin {
Ok(origin) => match origin.as_ref() {
Some(origin) => {
wp.set_origin(0);
self.instrp = origin.call(&wp, &atom, &mut args).await;
self.stack.extend(args);
}
None => return Err(Error::InvalidOrigin(origin_id)),
},
Err(_) => return Err(Error::InvalidOrigin(origin_id)),
}
}
Some(x) => {
let mut ptr = match self.stpop()? {
StackEntValue::Pointer(ptr) => ptr,
x => {
return Err(Error::StackedInvalidType {
expected: "pointer",
got: format!("{:?}", x),
});
})
}
}
};
let argc = match self.stpop()? {
StackEntValue::Int(i) => i,
x => {
return Err(Error::StackedInvalidType {
expected: "int",
got: format!("{:?}", x),
})
}
};
let ssl = self.stack.len();
let argstart = match usize::try_from(argc)
.ok()
.and_then(|argc| ssl.checked_sub(argc))
{
Some(x) => x,
None => return Err(Error::NotEnoughStacked),
};
let mut args = self.stack.drain(argstart..).collect();
let origin_id = ptr.origin();
ptr.set_origin(0);
self.instrp = match self.korigs[usize::from(origin_id)].read() {
Ok(origin) => match origin.as_ref() {
Some(origin) => origin.call(&ptr, &mut args).await,
None => return Err(Error::InvalidOrigin(origin_id)),
},
Err(_) => return Err(Error::InvalidOrigin(origin_id)),
};
self.verify_jumptarget(previptr, "call-r")?;
let rargc = u64::try_from(args.len()).unwrap();
self.stack.extend(args);
self.stack.push(StackEntValue::Int(rargc));
}
Instr::CallLocal(x) => {
if !self.instrp.is_call2jump() {
@ -205,15 +248,14 @@ impl Process {
Ok(y) => y,
Err(_) => return Err(Error::InstrpOutOfBounds),
};
let doit = match self.stack.pop() {
None => return Err(Error::NotEnoughStacked),
Some(StackEntValue::Int(i)) => i != 0,
Some(StackEntValue::Atom(a)) => a != Atom([0; 16]),
Some(z) => {
let doit = match self.stpop()? {
StackEntValue::Int(i) => i != 0,
StackEntValue::Atom(a) => a != Atom([0; 16]),
z => {
return Err(Error::StackedInvalidType {
expected: "atom | int",
got: format!("{:?}", z),
});
})
}
};
if doit {
@ -259,23 +301,8 @@ impl Process {
};
core::mem::swap(&mut y[0], &mut z[0]);
}
Instr::ABuild => {
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((b, a).into()));
}
x => {
return Err(Error::StackedInvalidType {
expected: "[int,int]",
got: format!("{:?}", x),
})
}
}
}
Instr::ADecon => match self.stack.pop() {
Some(StackEntValue::Atom(atom)) => {
Instr::ADecon => match self.stpop()? {
StackEntValue::Atom(atom) => {
let (b, a) = atom.into();
self.stack.push(StackEntValue::Int(b));
self.stack.push(StackEntValue::Int(a));
@ -287,49 +314,104 @@ impl Process {
})
}
},
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)),
}))
Instr::DoMath1(ubo) => {
use consts::MathUnOp as U;
use StackEntValue as V;
let x = self.stpop()?;
self.stack.push(match (x, ubo) {
(V::Int(x), U::Not) => V::Int(!x),
(V::Atom(x), U::Not) => V::Atom(Atom::from(!u128::from(x))),
(V::Bytes(mut x), U::Not) => {
x.iter_mut().for_each(|i| *i = !*i);
V::Bytes(x)
}
(Some(StackEntValue::Bytes(mut a)), Some(StackEntValue::Bytes(mut b))) => {
StackEntValue::Bytes(match mbo {
MathBinOp::NotAnd if a.len() == b.len() => {
a.iter_mut()
.zip(b.into_iter())
.for_each(|(x, y)| *x = !(*x & y));
a
}
MathBinOp::Add => {
a.append(&mut b);
a
}
_ => {
return Err(Error::StackedInvalidType {
expected: "[int,int] | [atom,atom]",
got: "[bytes, bytes]".to_string(),
})
}
(x, _) => {
return Err(Error::StackedInvalidType {
expected: "int|atom|bytes",
got: format!("{:?}", x),
})
}
x => {
});
}
Instr::DoMath2(mbo) => {
let b = self.stpop()?;
let a = self.stpop()?;
use consts::MathBinOp as B;
use StackEntValue as V;
self.stack.push(match (a, b) {
(V::Int(a), V::Int(b)) => match mbo {
B::Concat => V::Atom(Atom::from((a, b))),
B::And => V::Int(a & b),
B::Or => V::Int(a | b),
B::Xor => V::Int(a ^ b),
B::Add => V::Int(a.wrapping_add(b)),
B::Sub => V::Int(a.wrapping_sub(b)),
B::Mul => {
V::Atom(Atom::from(u128::from(a).wrapping_mul(u128::from(b))))
}
B::Div => V::Int(match a.checked_div(b) {
Some(x) => x,
None => return Err(Error::DivisionByZero),
}),
B::Rem => V::Int(match a.checked_rem(b) {
Some(x) => x,
None => return Err(Error::DivisionByZero),
}),
},
(V::Atom(a), V::Atom(b)) => {
V::Atom(Atom::from(u128_binmath(u128::from(a), u128::from(b), mbo)?))
}
(V::Int(a), V::Atom(b)) => {
V::Atom(Atom::from(u128_binmath(u128::from(a), u128::from(b), mbo)?))
}
(V::Atom(a), V::Int(b)) => {
V::Atom(Atom::from(u128_binmath(u128::from(a), u128::from(b), mbo)?))
}
(V::Bytes(a), V::Bytes(b)) => V::Bytes(match mbo {
B::Concat => {
let mut x = Vec::with_capacity(a.len() + b.len());
x.extend_from_slice(&a);
x.extend_from_slice(&b);
x
}
B::And => {
let mut x: Vec<_> =
a.iter().zip(b.iter()).map(|(w, v)| w & v).collect();
let minp = x.len();
x.extend_from_slice(
&(if a.len() < b.len() { b } else { a })[minp..],
);
x
}
B::Or => {
let mut x: Vec<_> =
a.iter().zip(b.iter()).map(|(w, v)| w | v).collect();
let minp = x.len();
x.extend_from_slice(
&(if a.len() < b.len() { b } else { a })[minp..],
);
x
}
B::Xor => {
let mut x: Vec<_> =
a.iter().zip(b.iter()).map(|(w, v)| w ^ v).collect();
let minp = x.len();
x.extend_from_slice(
&(if a.len() < b.len() { b } else { a })[minp..],
);
x
}
_ => {
return Err(Error::StackedInvalidType {
expected: "[int|atom, int|atom]",
got: "[bytes, bytes]".to_string(),
})
}
}),
(a, b) => {
return Err(Error::StackedInvalidType {
expected: "[int,int] | [atom,atom] | [bytes,bytes]",
got: format!("{:?}", x),
expected: "[int|atom, int|atom] | [bytes,bytes]",
got: format!("{:?}, {:?}", a, b),
})
}
});