essential_asm/
lib.rs

1//! Assembly for the Essential VM.
2//!
3//! # Op Table
4#![doc = essential_asm_gen::gen_ops_docs_table!()]
5#![cfg_attr(not(feature = "std"), no_std)]
6#![forbid(unsafe_code)]
7#![warn(missing_docs)]
8
9use core::fmt;
10#[doc(inline)]
11pub use essential_types::Word;
12#[doc(inline)]
13pub use op::{Op, *};
14#[doc(inline)]
15pub use opcode::{InvalidOpcodeError, NotEnoughBytesError, Op as Opcode};
16
17/// Determine the effects of a program.
18pub mod effects;
19
20/// Typed representation of an operation its associated data.
21mod op {
22    /// Operation types that may be converted to their serialized form in bytes.
23    pub trait ToBytes {
24        /// The iterator yielding bytes.
25        type Bytes: IntoIterator<Item = u8>;
26        /// Convert the operation to its serialized form in bytes.
27        fn to_bytes(&self) -> Self::Bytes;
28    }
29
30    /// Allows for converting an `Op` into its associated `Opcode`.
31    pub trait ToOpcode {
32        /// The associated `Opcode` type.
33        type Opcode;
34        /// The `opcode` associated with this operation.
35        fn to_opcode(&self) -> Self::Opcode;
36    }
37
38    /// Operation types that may be parsed from a bytecode representation.
39    pub trait TryFromBytes: Sized {
40        /// Represents any error that might occur while parsing an op from bytes.
41        type Error: core::fmt::Debug + core::fmt::Display;
42        /// Parse a single operation from the given iterator yielding bytes.
43        ///
44        /// Returns `None` in the case that the given iterator is empty.
45        fn try_from_bytes(
46            bytes: &mut impl Iterator<Item = u8>,
47        ) -> Option<Result<Self, Self::Error>>;
48    }
49
50    essential_asm_gen::gen_all_op_decls!();
51    essential_asm_gen::gen_all_op_impls!();
52
53    /// Provides the operation type bytes iterators.
54    pub mod bytes_iter {
55        essential_asm_gen::gen_all_op_bytes_iter!();
56    }
57
58    /// Short hand names for the operations.
59    pub mod short {
60        use super::{Op, *};
61        essential_asm_gen::gen_all_op_consts!();
62    }
63}
64
65/// Typed representation of the opcode, without any associated data.
66pub mod opcode {
67    use core::fmt;
68
69    /// Parse the operation associated with the opcode.
70    pub trait ParseOp {
71        /// The operation associated with the opcode.
72        type Op;
73        /// Any error that might occur while parsing.
74        type Error: core::fmt::Debug + core::fmt::Display;
75        /// Attempt to parse the operation associated with the opcode from the given bytes.
76        ///
77        /// Only consumes the bytes necessary to construct any associated data.
78        ///
79        /// Returns an error in the case that the given `bytes` iterator
80        /// contains insufficient bytes to parse the op.
81        fn parse_op(&self, bytes: &mut impl Iterator<Item = u8>) -> Result<Self::Op, Self::Error>;
82    }
83
84    /// An attempt to parse a byte as an opcode failed.
85    #[derive(Debug)]
86    pub struct InvalidOpcodeError(pub u8);
87
88    /// An error occurring within `Opcode::parse_op` in the case that the
89    /// provided bytes iterator contains insufficient bytes for the expected
90    /// associated operation data.
91    #[derive(Debug)]
92    pub struct NotEnoughBytesError;
93
94    impl fmt::Display for InvalidOpcodeError {
95        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
96            write!(f, "Invalid Opcode 0x{:02X}", self.0)
97        }
98    }
99
100    impl fmt::Display for NotEnoughBytesError {
101        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
102            write!(f, "Provided iterator did not yield enough bytes")
103        }
104    }
105
106    #[cfg(feature = "std")]
107    impl std::error::Error for InvalidOpcodeError {}
108
109    #[cfg(feature = "std")]
110    impl std::error::Error for NotEnoughBytesError {}
111
112    essential_asm_gen::gen_all_opcode_decls!();
113    essential_asm_gen::gen_all_opcode_impls!();
114}
115
116/// Errors that can occur while parsing ops from bytes.
117#[derive(Debug)]
118pub enum FromBytesError {
119    /// An invalid opcode was encountered.
120    InvalidOpcode(InvalidOpcodeError),
121    /// The bytes iterator did not contain enough bytes for a particular operation.
122    NotEnoughBytes(NotEnoughBytesError),
123}
124
125impl fmt::Display for FromBytesError {
126    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
127        f.write_str("failed to parse ops from bytes: ")?;
128        match self {
129            Self::InvalidOpcode(err) => err.fmt(f),
130            Self::NotEnoughBytes(err) => err.fmt(f),
131        }
132    }
133}
134
135#[cfg(feature = "std")]
136impl std::error::Error for FromBytesError {}
137
138impl From<InvalidOpcodeError> for FromBytesError {
139    fn from(err: InvalidOpcodeError) -> Self {
140        Self::InvalidOpcode(err)
141    }
142}
143
144impl From<NotEnoughBytesError> for FromBytesError {
145    fn from(err: NotEnoughBytesError) -> Self {
146        Self::NotEnoughBytes(err)
147    }
148}
149
150/// Parse operations from the given iterator yielding bytes.
151///
152/// Returns an iterator yielding `Op` results, erroring in the case that an
153/// invalid opcode is encountered or the iterator contains insufficient bytes
154/// for an operation.
155pub fn from_bytes(
156    bytes: impl IntoIterator<Item = u8>,
157) -> impl Iterator<Item = Result<Op, FromBytesError>> {
158    let mut iter = bytes.into_iter();
159    core::iter::from_fn(move || Op::try_from_bytes(&mut iter))
160}
161
162/// Convert the given iterator yielding operations into and iterator yielding
163/// the serialized form in bytes.
164pub fn to_bytes(ops: impl IntoIterator<Item = Op>) -> impl Iterator<Item = u8> {
165    ops.into_iter().flat_map(|op| op.to_bytes())
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171
172    #[test]
173    fn opcode_roundtrip_u8() {
174        for byte in 0..=u8::MAX {
175            if let Ok(opcode) = Opcode::try_from(byte) {
176                println!("{byte:02X}: {opcode:?}");
177                assert_eq!(u8::from(opcode), byte);
178            }
179        }
180    }
181
182    fn roundtrip(ops: Vec<Op>) {
183        assert!(!ops.is_empty());
184        let bytes: Vec<_> = to_bytes(ops.iter().cloned()).collect();
185        assert_eq!(
186            ops,
187            from_bytes(bytes).collect::<Result<Vec<_>, _>>().unwrap()
188        );
189    }
190
191    #[test]
192    fn roundtrip_args_start() {
193        let ops: Vec<Op> = vec![
194            Stack::Push(0x1234567812345678).into(),
195            Stack::Push(0x0F0F0F0F0F0F0F0F).into(),
196            Memory::Load.into(),
197            Memory::Store.into(),
198        ];
199        roundtrip(ops);
200    }
201
202    #[test]
203    fn roundtrip_args_end() {
204        let ops: Vec<Op> = vec![
205            Stack::Swap.into(),
206            Stack::Dup.into(),
207            Stack::Push(0x0F0F0F0F0F0F0F0F).into(),
208        ];
209        roundtrip(ops);
210    }
211
212    #[test]
213    fn roundtrip_args_interspersed() {
214        let ops: Vec<Op> = vec![
215            Stack::Push(0x1234567812345678).into(),
216            Stack::Swap.into(),
217            Stack::Push(0x0F0F0F0F0F0F0F0F).into(),
218            Stack::Dup.into(),
219            Stack::Push(0x1234567812345678).into(),
220        ];
221        roundtrip(ops);
222    }
223
224    #[test]
225    fn roundtrip_no_args() {
226        let ops: Vec<Op> = vec![
227            Memory::Store.into(),
228            Access::ThisAddress.into(),
229            Memory::Load.into(),
230            Access::ThisContractAddress.into(),
231            Access::PredicateDataLen.into(),
232        ];
233        roundtrip(ops);
234    }
235
236    fn expect_invalid_opcode(opcode_byte: u8) {
237        let bytes = vec![opcode_byte];
238        let err = from_bytes(bytes)
239            .collect::<Result<Vec<_>, _>>()
240            .unwrap_err();
241        match err {
242            FromBytesError::InvalidOpcode(InvalidOpcodeError(byte)) => {
243                assert_eq!(byte, opcode_byte)
244            }
245            _ => panic!("unexpected error variant"),
246        }
247    }
248
249    #[test]
250    fn invalid_opcode_0x00() {
251        let opcode_byte = 0x00;
252        expect_invalid_opcode(opcode_byte);
253    }
254
255    #[test]
256    fn invalid_opcode_0xff() {
257        let opcode_byte = 0xFF;
258        expect_invalid_opcode(opcode_byte);
259    }
260
261    #[test]
262    fn not_enough_bytes() {
263        let opcode_byte = opcode::Stack::Push as u8;
264        let bytes = vec![opcode_byte];
265        let err = from_bytes(bytes)
266            .collect::<Result<Vec<_>, _>>()
267            .unwrap_err();
268        match err {
269            FromBytesError::NotEnoughBytes(_) => (),
270            _ => panic!("unexpected error variant"),
271        }
272    }
273}