#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use thiserror::Error;
use super::types::{Instruction, Register};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
pub enum DecodeError {
#[error("instruction stream truncated: empty input")]
EmptyInput,
#[error("unknown XQVM opcode 0x{byte:02X}")]
UnknownOpcode {
byte: u8,
},
#[error(
"instruction stream truncated: opcode 0x{opcode:02X} needs {needed} more byte(s), got {available}"
)]
TruncatedOperand {
opcode: u8,
needed: usize,
available: usize,
},
}
trait EncodeOperand {
fn encode_into(&self, buf: &mut Vec<u8>);
}
trait DecodeOperand: Sized {
fn decode_from(bytes: &[u8], opcode: u8) -> Result<(Self, usize), DecodeError>;
}
impl EncodeOperand for u8 {
fn encode_into(&self, buf: &mut Vec<u8>) {
buf.push(*self);
}
}
impl DecodeOperand for u8 {
fn decode_from(bytes: &[u8], opcode: u8) -> Result<(Self, usize), DecodeError> {
bytes
.first()
.copied()
.map(|v| (v, 1))
.ok_or(DecodeError::TruncatedOperand {
opcode,
needed: 1,
available: bytes.len(),
})
}
}
impl EncodeOperand for u16 {
fn encode_into(&self, buf: &mut Vec<u8>) {
buf.extend_from_slice(&self.to_be_bytes());
}
}
impl DecodeOperand for u16 {
fn decode_from(bytes: &[u8], opcode: u8) -> Result<(Self, usize), DecodeError> {
let slice = bytes.get(..2).ok_or(DecodeError::TruncatedOperand {
opcode,
needed: 2,
available: bytes.len(),
})?;
let arr: [u8; 2] = slice
.try_into()
.unwrap_or_else(|_| unreachable!("slice length 2 always converts to [u8; 2]"));
Ok((Self::from_be_bytes(arr), 2))
}
}
impl EncodeOperand for i16 {
fn encode_into(&self, buf: &mut Vec<u8>) {
buf.extend_from_slice(&self.to_be_bytes());
}
}
impl DecodeOperand for i16 {
fn decode_from(bytes: &[u8], opcode: u8) -> Result<(Self, usize), DecodeError> {
let slice = bytes.get(..2).ok_or(DecodeError::TruncatedOperand {
opcode,
needed: 2,
available: bytes.len(),
})?;
let arr: [u8; 2] = slice
.try_into()
.unwrap_or_else(|_| unreachable!("slice length 2 always converts to [u8; 2]"));
Ok((Self::from_be_bytes(arr), 2))
}
}
impl EncodeOperand for i64 {
fn encode_into(&self, buf: &mut Vec<u8>) {
buf.extend_from_slice(&self.to_be_bytes());
}
}
impl DecodeOperand for i64 {
fn decode_from(bytes: &[u8], opcode: u8) -> Result<(Self, usize), DecodeError> {
let slice = bytes.get(..8).ok_or(DecodeError::TruncatedOperand {
opcode,
needed: 8,
available: bytes.len(),
})?;
let arr: [u8; 8] = slice
.try_into()
.unwrap_or_else(|_| unreachable!("slice length 8 always converts to [u8; 8]"));
Ok((Self::from_be_bytes(arr), 8))
}
}
impl EncodeOperand for Register {
fn encode_into(&self, buf: &mut Vec<u8>) {
buf.push(self.0);
}
}
impl DecodeOperand for Register {
fn decode_from(bytes: &[u8], opcode: u8) -> Result<(Self, usize), DecodeError> {
let (slot, n) = u8::decode_from(bytes, opcode)?;
Ok((Self(slot), n))
}
}
impl<const N: usize> EncodeOperand for [u8; N] {
fn encode_into(&self, buf: &mut Vec<u8>) {
buf.extend_from_slice(self);
}
}
impl<const N: usize> DecodeOperand for [u8; N] {
fn decode_from(bytes: &[u8], opcode: u8) -> Result<(Self, usize), DecodeError> {
let slice = bytes.get(..N).ok_or(DecodeError::TruncatedOperand {
opcode,
needed: N,
available: bytes.len(),
})?;
let arr: Self = slice
.try_into()
.unwrap_or_else(|_| unreachable!("slice length N always converts to [u8; N]"));
Ok((arr, N))
}
}
macro_rules! impl_codec {
( $( ($code:literal, $variant:ident, $mnem:literal, $doc:literal,
$_delta:expr, {$($fname:ident: $ftype:ty),*}) ),* $(,)? ) => {
pub fn encode(instr: &Instruction) -> Vec<u8> {
let mut buf = Vec::new();
match instr {
$(
Instruction::$variant { $($fname,)* } => {
buf.push($code as u8);
$( EncodeOperand::encode_into($fname, &mut buf); )*
}
)*
}
buf
}
pub fn decode(bytes: &[u8]) -> Result<(Instruction, usize), DecodeError> {
let opcode = *bytes.first().ok_or(DecodeError::EmptyInput)?;
let payload = bytes.get(1..).unwrap_or(&[]);
let mut pos = 0usize;
match opcode {
$(
$code => {
$(
let ($fname, _n) = <$ftype as DecodeOperand>::decode_from(
payload.get(pos..).unwrap_or(&[]),
opcode,
)?;
pos += _n;
)*
Ok((Instruction::$variant { $($fname,)* }, pos + 1))
}
)*
unknown => Err(DecodeError::UnknownOpcode { byte: unknown }),
}
}
};
}
opcodes!(impl_codec);
#[cfg(test)]
mod tests {
use super::*;
use crate::bytecode::types::Register;
macro_rules! all_instructions {
( $( ($code:literal, $variant:ident, $mnem:literal, $doc:literal,
$_delta:expr, {$($fname:ident: $ftype:ty),*}) ),* $(,)? ) => {
[ $( Instruction::$variant { $($fname: <$ftype as Default>::default(),)* } ),* ]
};
}
#[test]
fn encode_decode_roundtrip_all_87() {
for instr in opcodes!(all_instructions) {
let bytes = encode(&instr);
let (decoded, consumed) = decode(&bytes).expect("decode failed");
assert_eq!(decoded, instr, "roundtrip mismatch for {instr:?}");
assert_eq!(
consumed,
bytes.len(),
"consumed != encoded length for {instr:?}"
);
}
}
#[test]
fn halt_is_one_byte() {
assert_eq!(encode(&Instruction::Halt {}), [0xFF]);
}
#[test]
fn nop_is_one_byte() {
assert_eq!(encode(&Instruction::Nop {}), [0xF0]);
}
#[test]
fn pop_is_one_byte() {
assert_eq!(encode(&Instruction::Pop {}), [0x10]);
}
#[test]
fn push1_is_two_bytes() {
assert_eq!(encode(&Instruction::Push1 { val: [42] }), [0x11, 42]);
}
#[test]
fn push2_is_three_bytes() {
assert_eq!(
encode(&Instruction::Push2 { val: [0x00, 0x80] }),
[0x12, 0x00, 0x80]
);
}
#[test]
fn push3_is_four_bytes() {
assert_eq!(
encode(&Instruction::Push3 {
val: [0x01, 0x02, 0x03]
}),
[0x13, 0x01, 0x02, 0x03]
);
}
#[test]
fn push8_is_nine_bytes() {
let v = 1_000_000_000_000i64.to_be_bytes();
let mut expected = [0u8; 9];
expected[0] = 0x18;
expected[1..].copy_from_slice(&v);
assert_eq!(encode(&Instruction::Push8 { val: v }), expected);
}
#[test]
fn jump2_label_fixint_be() {
let bytes = encode(&Instruction::Jump2 { label: 5u16 });
assert_eq!(bytes, [0x03, 0x00, 0x05]);
}
#[test]
fn jump1_label_is_two_bytes() {
let bytes = encode(&Instruction::Jump1 { label: 7u8 });
assert_eq!(bytes, [0x01, 0x07]);
}
#[test]
fn jumpi1_label_is_two_bytes() {
let bytes = encode(&Instruction::JumpI1 { label: 0u8 });
assert_eq!(bytes, [0x02, 0x00]);
}
#[test]
fn energy_two_register_bytes() {
let bytes = encode(&Instruction::Energy {
model: Register(2),
sample: Register(3),
});
assert_eq!(bytes, [0x7F, 2, 3]);
}
#[test]
fn unknown_opcode_returns_error() {
assert!(matches!(
decode(&[0x0Du8]),
Err(DecodeError::UnknownOpcode { byte: 0x0D })
));
}
#[test]
fn empty_input_returns_error() {
assert!(matches!(decode(&[]), Err(DecodeError::EmptyInput)));
}
#[test]
fn truncated_operand_returns_error() {
assert!(matches!(
decode(&[0x18u8]),
Err(DecodeError::TruncatedOperand {
opcode: 0x18,
needed: 8,
available: 0,
})
));
assert!(matches!(
decode(&[0x0Au8]),
Err(DecodeError::TruncatedOperand {
opcode: 0x0A,
needed: 1,
available: 0,
})
));
assert!(matches!(
decode(&[0x03u8, 0x00]),
Err(DecodeError::TruncatedOperand {
opcode: 0x03,
needed: 2,
available: 1,
})
));
}
#[test]
fn encode_sequence_then_decode_each() {
let program = [
Instruction::Push1 { val: [5] },
Instruction::Push1 { val: [3] },
Instruction::Add {},
Instruction::Halt {},
];
let mut buf = Vec::new();
for instr in &program {
buf.extend_from_slice(&encode(instr));
}
let mut pos = 0usize;
for expected in &program {
let (got, n) = decode(&buf[pos..]).unwrap();
assert_eq!(got, *expected);
pos += n;
}
assert_eq!(pos, buf.len());
}
}