API BREAK: bytecode: get rid of Label

This commit is contained in:
Alain Zscheile 2022-10-23 00:01:45 +02:00
parent e83e9f8394
commit 4ff3b98de9
3 changed files with 30 additions and 58 deletions

View file

@ -5,7 +5,6 @@ use int_enum::IntEnum;
#[derive(Clone, Copy, Debug, PartialEq, Eq, IntEnum)]
pub enum OpType {
// control flow
Label = 0x4c, /* L */
CallRemote = 0x43, /* C */
CallLocal = 0x49, /* I */
CallLDefer = 0x64, /* d */

View file

@ -4,9 +4,6 @@ use core::fmt;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Instr {
// control flow
/// defines a destination label
Label,
/// pops the descriptor table pointer from the stack,
/// 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
@ -64,7 +61,6 @@ impl Instr {
#[inline]
pub fn typ(&self) -> OpType {
match self {
Instr::Label => OpType::Label,
Instr::CallRemote => OpType::CallRemote,
Instr::CallLocal(_) => OpType::CallLocal,
Instr::CallLDefer(_) => OpType::CallLDefer,
@ -119,7 +115,6 @@ impl<'a> crate::Parse<'a> for Instr {
OpType::Shift => i8::parse(inp).map(|(inp, shv)| (inp, Instr::Shift(shv))),
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::DupFrom => Ok((inp, Instr::DupFrom)),
@ -144,7 +139,7 @@ impl Instr {
Instr::Shift(shv) => writer.write_all(&shv.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::DupFrom => Ok(()),
Instr::CallRemote | Instr::Return | Instr::DupFrom => Ok(()),
}
}
}

View file

@ -23,7 +23,6 @@ pub type StackEntValue = u64;
#[derive(Clone, Debug)]
pub enum Error<'m> {
InstrpOutOfBounds,
UnparsableInstruction(&'m [u8]),
OutOfFuel,
NotEnoughStacked,
@ -33,7 +32,6 @@ pub enum Error<'m> {
impl fmt::Display for Error<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InstrpOutOfBounds => write!(f, "instruction pointer out-of-bounds"),
Self::UnparsableInstruction(x) => write!(f, "reached unparsable instruction: {:x?}", x),
Self::OutOfFuel => write!(f, "out of fuel"),
Self::NotEnoughStacked => write!(f, "not enough operands on stack"),
@ -50,7 +48,6 @@ pub struct VerifyError<'m> {
#[derive(Clone, Debug)]
pub enum VerifyErrorKind<'m> {
InvalidJumpTarget { invoked_by: &'static str, to: usize },
InstrpOutOfBounds,
UnparsableInstruction(&'m [u8]),
}
@ -60,9 +57,6 @@ impl fmt::Display for VerifyError<'_> {
use VerifyErrorKind as K;
write!(f, "at instruction {}: ", self.from)?;
match &self.kind {
K::InvalidJumpTarget { invoked_by, to } => {
write!(f, "`{}` arrived at non-jump target `{}`", invoked_by, to)
}
K::InstrpOutOfBounds => write!(f, "instruction pointer out-of-bounds"),
K::UnparsableInstruction(x) => write!(f, "reached unparsable instruction: {:x?}", x),
}
@ -73,25 +67,6 @@ impl fmt::Display for VerifyError<'_> {
/// that jump targets are valid (this makes it possible to avoid unnecessary
/// checks during tight loops)
pub fn verify(m: &[u8]) -> Result<(), VerifyError<'_>> {
let check_jump =
|jinstr: &'static str, from: usize, to: u64| -> Result<(), VerifyError<'static>> {
match usize::try_from(to) {
Ok(to) if to == 0 => Ok(()),
Ok(to) if matches!(next_instr(m, to), Some(Ok((_, Instr::Label)))) => Ok(()),
Ok(to) => Err(VerifyError {
from,
kind: VerifyErrorKind::InvalidJumpTarget {
invoked_by: jinstr,
to,
},
}),
Err(_) => Err(VerifyError {
from,
kind: VerifyErrorKind::InstrpOutOfBounds,
}),
}
};
let mut instrp = 0;
while let Some(nxti) = next_instr(m, instrp) {
@ -102,11 +77,24 @@ pub fn verify(m: &[u8]) -> Result<(), VerifyError<'_>> {
assert_ne!(nxtidelta, 0);
instrp += nxtidelta;
match nxti {
Instr::CallLocal(x) => check_jump("call-l", instrp, x)?,
Instr::CallLDefer(x) => check_jump("call-l-defer", instrp, x)?,
Instr::JumpCond(x) => check_jump("jump-cond", instrp, x)?,
Instr::Label
| Instr::CallRemote
Instr::CallLocal(x) | Instr::CallLDefer(x) | Instr::JumpCond(x) => {
match usize::try_from(x) {
Err(_) => {
return Err(VerifyError {
from: usize::MAX,
kind: VerifyErrorKind::InstrpOutOfBounds,
})
}
Ok(x) if x >= m.len() => {
return Err(VerifyError {
from: x,
kind: VerifyErrorKind::InstrpOutOfBounds,
})
}
Ok(_) => {}
}
}
Instr::CallRemote
| Instr::Return
| Instr::Push(_)
| Instr::Pop(_)
@ -144,14 +132,11 @@ impl<'m> Process<'m> {
}
**x -= 1;
}
let (nxtidelta, nxti) = match next_instr(self.m, self.instrp) {
None => return Err(Error::InstrpOutOfBounds),
Some(Err(code)) => return Err(Error::UnparsableInstruction(code)),
Some(Ok(x)) => x,
};
let (nxtidelta, nxti) = next_instr(self.m, self.instrp)
.unwrap()
.map_err(Error::UnparsableInstruction)?;
self.instrp += nxtidelta;
match nxti {
Instr::Label => {}
Instr::CallRemote => {
if !is_call2jump(self.m, self.instrp) {
self.callstack.push(self.instrp);
@ -162,28 +147,19 @@ impl<'m> Process<'m> {
if !is_call2jump(self.m, self.instrp) {
self.callstack.push(self.instrp);
}
self.instrp = match x.try_into() {
Ok(y) => y,
Err(_) => return Err(Error::InstrpOutOfBounds),
};
self.instrp = x.try_into().unwrap();
}
Instr::CallLDefer(x) => match x.try_into() {
Ok(jtip) => {
Instr::CallLDefer(x) => {
let jtip = x.try_into().unwrap();
if is_call2jump(self.m, self.instrp) {
self.instrp = jtip;
} else {
self.callstack.push(jtip);
}
}
Err(_) => return Err(Error::InstrpOutOfBounds),
},
}
Instr::JumpCond(x) => {
let x: usize = match x.try_into() {
Ok(y) => y,
Err(_) => return Err(Error::InstrpOutOfBounds),
};
if self.stpop()? != 0 {
self.instrp = x;
self.instrp = x.try_into().unwrap();
}
}
Instr::Return => match self.callstack.pop() {
@ -288,7 +264,9 @@ mod tests {
#[test]
fn doesnt_crash(inp in proptest::collection::vec(0..=u8::MAX, 0..1024)) {
let _ = verify(&inp[..]);
if verify(&inp[..]).is_err() {
return Ok(());
}
let mut p = Process {
stack: Vec::new(),
callstack: Vec::new(),