fuel_etk_asm/
ops.rs

1//! Definitions of all instructions supported by the assembler.
2
3mod error {
4    use super::expression;
5    use etk_ops::london::Op;
6    use num_bigint::BigInt;
7    use snafu::{Backtrace, Snafu};
8
9    #[derive(Snafu, Debug)]
10    #[snafu(context(suffix(false)), visibility(pub(crate)))]
11    pub enum Error {
12        ContextIncomplete {
13            #[snafu(backtrace)]
14            source: expression::Error,
15        },
16        ExpressionTooLarge {
17            source: std::array::TryFromSliceError,
18            value: BigInt,
19            spec: Op<()>,
20            backtrace: Backtrace,
21        },
22        ExpressionNegative {
23            value: BigInt,
24            backtrace: Backtrace,
25        },
26    }
27
28    /// The error that can arise while parsing a specifier from a string.
29    #[derive(Debug, Snafu)]
30    #[snafu(display("unknown specifier: {}", text))]
31    #[snafu(context(suffix(Context)), visibility(pub(super)))]
32    #[non_exhaustive]
33    pub struct UnknownSpecifierError {
34        text: String,
35        backtrace: Backtrace,
36    }
37}
38
39pub(crate) mod expression;
40pub(crate) mod imm;
41mod macros;
42mod types;
43
44pub(crate) use self::error::Error;
45
46use etk_ops::london::{Op, Operation, Push32};
47
48pub use self::error::UnknownSpecifierError;
49pub use self::expression::{Context, Expression, Terminal};
50pub use self::imm::{Imm, TryFromSliceError};
51
52pub use self::macros::{
53    ExpressionMacroDefinition, ExpressionMacroInvocation, InstructionMacroDefinition,
54    InstructionMacroInvocation, MacroDefinition,
55};
56pub use self::types::Abstract;
57
58use std::cmp::{Eq, PartialEq};
59use std::convert::{TryFrom, TryInto};
60use std::fmt;
61
62use snafu::{ensure, ResultExt};
63
64pub(crate) trait Assemble {
65    fn assemble(&self, buf: &mut Vec<u8>);
66}
67
68impl<T> Assemble for T
69where
70    T: Operation,
71    T::ImmediateRef: AsRef<[u8]>,
72{
73    fn assemble(&self, buf: &mut Vec<u8>) {
74        buf.push(self.code_byte());
75        if let Some(immediate) = self.immediate().map(AsRef::as_ref) {
76            buf.extend_from_slice(immediate);
77        }
78    }
79}
80
81/// Allows to concretize operations.
82pub trait Concretize {
83    /// Concrete operation type.
84    type Concrete;
85
86    /// Concretizes a specific operation.
87    fn concretize(&self, ctx: Context) -> Result<Self::Concrete, error::Error>;
88}
89
90impl Concretize for Op<Abstract> {
91    type Concrete = Op<[u8]>;
92
93    fn concretize(&self, ctx: Context) -> Result<Self::Concrete, error::Error> {
94        let expr = match self.immediate() {
95            Some(i) => &i.tree,
96            None => return Ok(Op::new(self.code()).unwrap()),
97        };
98
99        let value = expr
100            .eval_with_context(ctx)
101            .context(error::ContextIncomplete)?;
102
103        let (sign, mut bytes) = value.to_bytes_be();
104
105        ensure!(
106            sign != num_bigint::Sign::Minus,
107            error::ExpressionNegative { value }
108        );
109
110        if bytes.len() < self.extra_len() {
111            let mut new = vec![0u8; self.extra_len() - bytes.len()];
112            new.append(&mut bytes);
113            bytes = new;
114        }
115
116        let result = self
117            .code()
118            .with(bytes.as_slice())
119            .context(error::ExpressionTooLarge {
120                value,
121                spec: self.code(),
122            })?;
123
124        Ok(result)
125    }
126}
127
128trait Expr {
129    fn expr(&self) -> Option<&Expression>;
130    fn expr_mut(&mut self) -> Option<&mut Expression>;
131}
132
133impl<T> Expr for T
134where
135    T: Operation<ImmediateRef = Imm>,
136{
137    fn expr(&self) -> Option<&Expression> {
138        self.immediate().map(|i| &i.tree)
139    }
140
141    fn expr_mut(&mut self) -> Option<&mut Expression> {
142        self.immediate_mut().map(|i| &mut i.tree)
143    }
144}
145
146/// The access mode (read, write, both) of an instruction.
147#[derive(Debug, Clone, Copy, Eq, PartialEq)]
148pub enum Access {
149    /// Indicates that the instruction might read.
150    Read,
151
152    /// Indicates that the instruction might write.
153    Write,
154
155    /// Indicates that the instruction may read and/or write.
156    ReadWrite,
157}
158
159impl Access {
160    /// Returns true if the instruction might read.
161    pub fn reads(self) -> bool {
162        matches!(self, Self::Read | Self::ReadWrite)
163    }
164
165    /// Returns true if the instruction might write.
166    pub fn writes(self) -> bool {
167        matches!(self, Self::Write | Self::ReadWrite)
168    }
169}
170
171/// Like an [`Op`], except it also supports virtual instructions.
172///
173/// In addition to the real EVM instructions, `AbstractOp` also supports defining
174/// labels, and pushing variable length immediate arguments.
175#[derive(Debug, Clone, Eq, PartialEq)]
176pub enum AbstractOp {
177    /// A real `Op`, as opposed to a label or variable sized push.
178    Op(Op<Abstract>),
179
180    /// A label, which is a virtual instruction.
181    Label(String),
182
183    /// A variable sized push, which is a virtual instruction.
184    Push(Imm),
185
186    /// A user-defined macro definition, which is a virtual instruction.
187    MacroDefinition(MacroDefinition),
188
189    /// A user-defined macro, which is a virtual instruction.
190    Macro(InstructionMacroInvocation),
191}
192
193impl AbstractOp {
194    /// Construct a new `AbstractOp` from an `Operation`.
195    pub fn new<O>(op: O) -> Self
196    where
197        O: Into<Op<Abstract>>,
198    {
199        Self::Op(op.into())
200    }
201
202    /// Concretizes operations.
203    pub fn concretize(self, ctx: Context) -> Result<Op<[u8]>, error::Error> {
204        match self {
205            Self::Op(op) => op.concretize(ctx),
206            Self::Push(imm) => {
207                let value = imm
208                    .tree
209                    .eval_with_context(ctx)
210                    .context(error::ContextIncomplete)?;
211
212                let (sign, bytes) = value.to_bytes_be();
213
214                ensure!(
215                    sign != num_bigint::Sign::Minus,
216                    error::ExpressionNegative { value }
217                );
218
219                if bytes.len() > 32 {
220                    // TODO: Fix hack to get a TryFromSliceError.
221                    let err = <[u8; 32]>::try_from(bytes.as_slice())
222                        .context(error::ExpressionTooLarge {
223                            value,
224                            spec: Push32(()),
225                        })
226                        .unwrap_err();
227                    return Err(err);
228                }
229
230                let size = std::cmp::max(1, (value.bits() + 8 - 1) / 8);
231                let spec = Op::<()>::push(size.try_into().unwrap()).unwrap();
232
233                let start = bytes.len() + 1 - spec.size();
234                AbstractOp::new(spec.with(&bytes[start..]).unwrap()).concretize(ctx)
235            }
236            Self::Label(_) => panic!("labels cannot be concretized"),
237            Self::Macro(_) => panic!("macros cannot be concretized"),
238            Self::MacroDefinition(_) => panic!("macro definitions cannot be concretized"),
239        }
240    }
241
242    /// The expression to be pushed on the stack. Only relevant for push instructions.
243    pub(crate) fn expr(&self) -> Option<&Expression> {
244        match self {
245            Self::Op(op) => op.expr(),
246            Self::Push(Imm { tree, .. }) => Some(tree),
247            _ => None,
248        }
249    }
250
251    /// The expression to be pushed on the stack. Only relevant for push instructions.
252    pub(crate) fn expr_mut(&mut self) -> Option<&mut Expression> {
253        match self {
254            Self::Op(op) => op.expr_mut(),
255            Self::Push(Imm { tree, .. }) => Some(tree),
256            _ => None,
257        }
258    }
259
260    /// Return the total encoded size for this instruction, including the
261    /// immediate if one is required.
262    ///
263    /// If the size of this instruction is undefined (for example a variable sized
264    /// push), this function returns `None`.
265    pub fn size(&self) -> Option<usize> {
266        match self {
267            Self::Op(op) => Some(op.size()),
268            Self::Label(_) => Some(0),
269            Self::Push(_) => None,
270            Self::Macro(_) => None,
271            Self::MacroDefinition(_) => None,
272        }
273    }
274
275    /// Return the specifier that corresponds to this `AbstractOp`.
276    pub fn specifier(&self) -> Option<Op<()>> {
277        match self {
278            Self::Op(op) => Some(op.code()),
279            _ => None,
280        }
281    }
282}
283
284impl From<Op<[u8]>> for AbstractOp {
285    fn from(cop: Op<[u8]>) -> Self {
286        let code = cop.code();
287        let cop = match cop.into_immediate() {
288            Some(i) => code.with(i).unwrap(),
289            None => Op::new(code).unwrap(),
290        };
291        Self::Op(cop)
292    }
293}
294
295impl From<InstructionMacroDefinition> for AbstractOp {
296    fn from(item: InstructionMacroDefinition) -> Self {
297        AbstractOp::MacroDefinition(item.into())
298    }
299}
300
301impl From<ExpressionMacroDefinition> for AbstractOp {
302    fn from(item: ExpressionMacroDefinition) -> Self {
303        AbstractOp::MacroDefinition(item.into())
304    }
305}
306
307impl fmt::Display for AbstractOp {
308    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
309        match self {
310            Self::Op(op) => {
311                write!(f, "{}", op.code())?;
312                if let Some(imm) = op.immediate() {
313                    write!(f, " {}", imm)?;
314                }
315                Ok(())
316            }
317            Self::Push(txt) => write!(f, r#"%push({})"#, txt),
318            Self::Label(lbl) => write!(f, r#"{}:"#, lbl),
319            Self::Macro(m) => write!(f, "{}", m),
320            Self::MacroDefinition(defn) => write!(f, "{}", defn),
321        }
322    }
323}
324
325#[cfg(test)]
326mod tests {
327    use std::convert::TryInto;
328
329    use super::*;
330
331    #[test]
332    fn u8_into_imm1() {
333        let x: u8 = 0xdc;
334        let imm: Imm = x.into();
335        let res: Imm = Terminal::Number(x.into()).into();
336        assert_eq!(imm, res);
337    }
338
339    #[test]
340    fn u16_try_into_imm1() {
341        let x: u16 = 0xFF;
342        let imm: Imm = x.try_into().unwrap();
343        let res: Imm = Terminal::Number(x.into()).into();
344        assert_eq!(imm, res);
345    }
346
347    #[test]
348    fn u8_into_imm2() {
349        let x: u8 = 0xdc;
350        let imm: Imm = x.into();
351        let res: Imm = Terminal::Number(x.into()).into();
352        assert_eq!(imm, res);
353    }
354
355    #[test]
356    fn u16_into_imm2() {
357        let x: u16 = 0xfedc;
358        let imm: Imm = x.into();
359        let res: Imm = Terminal::Number(x.into()).into();
360        assert_eq!(imm, res);
361    }
362
363    #[test]
364    fn u128_into_imm32() {
365        let x: u128 = 0x1023456789abcdef0123456789abcdef;
366        let imm: Imm = x.into();
367        let res: Imm = Terminal::Number(x.into()).into();
368        assert_eq!(imm, res);
369    }
370}