Skip to content

Commit

Permalink
Add FromIterator impls for opcode and instr (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
vlopes11 authored Nov 2, 2021
1 parent 7c8f4b6 commit cd2dac2
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 33 deletions.
53 changes: 52 additions & 1 deletion src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,11 @@ impl Instruction {
///
/// The caller must ensure that the slice is has at least `Self::LEN` bytes.
pub unsafe fn from_slice_unchecked(buf: &[u8]) -> Self {
debug_assert!(buf.len() >= 4);
debug_assert!(buf.len() >= Self::LEN);

let instr = fuel_types::bytes::from_slice_unchecked(buf);
let instr = u32::from_be_bytes(instr);

Self::from(instr)
}

Expand Down Expand Up @@ -190,6 +192,12 @@ impl From<u32> for Instruction {
}
}

impl From<[u8; Instruction::LEN]> for Instruction {
fn from(instruction: [u8; Instruction::LEN]) -> Self {
u32::from_be_bytes(instruction).into()
}
}

impl From<Opcode> for Instruction {
fn from(op: Opcode) -> Self {
u32::from(op).into()
Expand Down Expand Up @@ -318,6 +326,39 @@ impl Instruction {
unsafe { Ok(Self::from_slice_unchecked(bytes)) }
}
}

/// Create a set of `Instruction` from an iterator of bytes
///
/// If not padded to [`Self::LEN`], will consume the unaligned bytes but won't try to parse an
/// instruction from them.
pub fn from_bytes_iter<I>(bytes: I) -> Vec<Self>
where
I: IntoIterator<Item = u8>,
{
let mut bytes = bytes.into_iter();
let mut buf = [0u8; Self::LEN];
let mut ret = Vec::with_capacity(bytes.size_hint().0 / Self::LEN);

loop {
let n = bytes
.by_ref()
.take(Self::LEN)
.zip(buf.as_mut().iter_mut())
.fold(0, |n, (x, b)| {
*b = x;

n + 1
});

if n < Self::LEN {
break;
}

ret.push(Self::from(buf));
}

ret
}
}

#[cfg(feature = "std")]
Expand All @@ -332,3 +373,13 @@ impl iter::FromIterator<Instruction> for Vec<u8> {
.collect()
}
}

#[cfg(feature = "std")]
impl iter::FromIterator<Opcode> for Vec<Instruction> {
fn from_iter<T>(iter: T) -> Self
where
T: IntoIterator<Item = Opcode>,
{
iter.into_iter().map(Instruction::from).collect()
}
}
92 changes: 70 additions & 22 deletions src/opcode.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use fuel_types::{Immediate12, Immediate18, Immediate24, RegisterId, Word};

use core::convert::TryFrom;
use fuel_types::{bytes, Immediate12, Immediate18, Immediate24, RegisterId, Word};

#[cfg(feature = "std")]
use std::{io, iter};
Expand Down Expand Up @@ -1255,7 +1253,7 @@ pub enum Opcode {

impl Opcode {
/// Size of the struct when serialized into bytes
pub const BYTES_SIZE: usize = 4;
pub const LEN: usize = 4;

/// Create a new [`Opcode`] given the internal attributes
pub const fn new(instruction: Instruction) -> Self {
Expand Down Expand Up @@ -1355,21 +1353,20 @@ impl Opcode {

/// Create a `Opcode` from a slice of bytes
///
/// # Panics
/// # Safety
///
/// This function will panic if the length of the bytes is smaller than
/// [`Opcode::BYTES_SIZE`].
pub fn from_bytes_unchecked(bytes: &[u8]) -> Self {
assert!(Self::BYTES_SIZE <= bytes.len());
/// Reflects the requirements of [`bytes::from_slice_unchecked`]
pub unsafe fn from_bytes_unchecked(bytes: &[u8]) -> Self {
debug_assert!(Self::LEN <= bytes.len());

let op = bytes::from_slice_unchecked(bytes);
let op = u32::from_be_bytes(op);

<[u8; Self::BYTES_SIZE]>::try_from(&bytes[..Self::BYTES_SIZE])
.map(u32::from_be_bytes)
.map(Self::from)
.unwrap_or_else(|_| unreachable!())
op.into()
}

/// Convert the opcode to bytes representation
pub fn to_bytes(self) -> [u8; Self::BYTES_SIZE] {
pub fn to_bytes(self) -> [u8; Self::LEN] {
u32::from(self).to_be_bytes()
}

Expand Down Expand Up @@ -1550,16 +1547,50 @@ impl Opcode {
/// Create a `Opcode` from a slice of bytes
///
/// This function will fail if the length of the bytes is smaller than
/// [`Opcode::BYTES_SIZE`].
/// [`Opcode::LEN`].
pub fn from_bytes(bytes: &[u8]) -> io::Result<Self> {
if bytes.len() < Self::BYTES_SIZE {
if bytes.len() < Self::LEN {
Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"The provided buffer is not big enough!",
))
} else {
Ok(Self::from_bytes_unchecked(bytes))
// Safety: checked length
Ok(unsafe { Self::from_bytes_unchecked(bytes) })
}
}

/// Create a set of `Opcode` from an iterator of bytes
///
/// If not padded to [`Self::LEN`], will consume the unaligned bytes but won't try to parse an
/// opcode from them.
pub fn from_bytes_iter<I>(bytes: I) -> Vec<Self>
where
I: IntoIterator<Item = u8>,
{
let mut bytes = bytes.into_iter();
let mut buf = [0u8; Self::LEN];
let mut ret = Vec::with_capacity(bytes.size_hint().0 / Self::LEN);

loop {
let n = bytes
.by_ref()
.take(Self::LEN)
.zip(buf.as_mut().iter_mut())
.fold(0, |n, (x, b)| {
*b = x;

n + 1
});

if n < Self::LEN {
break;
}

ret.push(Self::from(buf));
}

ret
}
}

Expand All @@ -1569,6 +1600,12 @@ impl From<Instruction> for Opcode {
}
}

impl From<[u8; Opcode::LEN]> for Opcode {
fn from(b: [u8; Opcode::LEN]) -> Self {
u32::from_be_bytes(b).into()
}
}

impl From<u32> for Opcode {
fn from(instruction: u32) -> Self {
Self::new(Instruction::from(instruction))
Expand Down Expand Up @@ -1940,10 +1977,10 @@ impl From<Opcode> for u32 {
#[cfg(feature = "std")]
impl io::Read for Opcode {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
buf.chunks_exact_mut(4)
buf.chunks_exact_mut(Opcode::LEN)
.next()
.map(|chunk| chunk.copy_from_slice(&u32::from(*self).to_be_bytes()))
.map(|_| 4)
.map(|_| Opcode::LEN)
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::UnexpectedEof,
Expand All @@ -1956,16 +1993,17 @@ impl io::Read for Opcode {
#[cfg(feature = "std")]
impl io::Write for Opcode {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
buf.chunks_exact(4)
// Safety: checked length
buf.chunks_exact(Opcode::LEN)
.next()
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::UnexpectedEof,
"The provided buffer is not big enough!",
)
})
.map(|bytes| *self = Self::from_bytes_unchecked(bytes))
.map(|_| 4)
.map(|bytes| *self = unsafe { Self::from_bytes_unchecked(bytes) })
.map(|_| Opcode::LEN)
}

fn flush(&mut self) -> io::Result<()> {
Expand All @@ -1982,3 +2020,13 @@ impl iter::FromIterator<Opcode> for Vec<u8> {
iter.into_iter().map(Opcode::to_bytes).flatten().collect()
}
}

#[cfg(feature = "std")]
impl iter::FromIterator<Instruction> for Vec<Opcode> {
fn from_iter<T>(iter: T) -> Self
where
T: IntoIterator<Item = Instruction>,
{
iter.into_iter().map(Opcode::from).collect()
}
}
31 changes: 21 additions & 10 deletions tests/encoding.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#[test]
fn opcode() {
// TODO maybe split this test case into several smaller ones?
use fuel_asm::*;
use std::io::{Read, Write};

Expand Down Expand Up @@ -93,6 +94,13 @@ fn opcode() {

let bytes: Vec<u8> = data.iter().copied().collect();

let data_p = Opcode::from_bytes_iter(bytes.clone());
let data_q = Instruction::from_bytes_iter(bytes.clone());
let data_q: Vec<Opcode> = data_q.into_iter().collect();

assert_eq!(data, data_p);
assert_eq!(data, data_q);

let pairs = bytes.chunks(8).into_iter().map(|chunk| {
let mut arr = [0; core::mem::size_of::<Word>()];
arr.copy_from_slice(chunk);
Expand All @@ -107,7 +115,7 @@ fn opcode() {
assert_eq!(data, result);

let mut bytes: Vec<u8> = vec![];
let mut buffer = [0u8; 4];
let mut buffer = [0u8; Opcode::LEN];

for mut op in data.clone() {
op.read(&mut buffer)
Expand All @@ -128,7 +136,7 @@ fn opcode() {
assert_eq!(imm_op, imm_ins);

let op_p = Opcode::from(op_p);
let op_q = Opcode::from_bytes_unchecked(op_bytes.as_slice());
let op_q = unsafe { Opcode::from_bytes_unchecked(op_bytes.as_slice()) };

assert_eq!(op, Opcode::from(ins));
assert_eq!(op, op_p);
Expand All @@ -138,10 +146,10 @@ fn opcode() {

// Assert opcode can be created from big slices
op_bytes.extend_from_slice(&[0xff; 25]);
while op_bytes.len() > Opcode::BYTES_SIZE {
while op_bytes.len() > Opcode::LEN {
op_bytes.pop();

let op_r = Opcode::from_bytes_unchecked(op_bytes.as_slice());
let op_r = unsafe { Opcode::from_bytes_unchecked(op_bytes.as_slice()) };
let op_s = Opcode::from_bytes(op_bytes.as_slice())
.expect("Failed to safely generate op from bytes!");

Expand All @@ -165,10 +173,13 @@ fn opcode() {
}

let mut op_p = Opcode::Undefined;
bytes.chunks(4).zip(data.iter()).for_each(|(chunk, op)| {
op_p.write(chunk)
.expect("Failed to parse opcode from chunk");

assert_eq!(op, &op_p);
});
bytes
.chunks(Opcode::LEN)
.zip(data.iter())
.for_each(|(chunk, op)| {
op_p.write(chunk)
.expect("Failed to parse opcode from chunk");

assert_eq!(op, &op_p);
});
}

0 comments on commit cd2dac2

Please sign in to comment.