triton_opcodes/
program.rs

1use std::fmt::Display;
2use std::io::Cursor;
3
4use anyhow::bail;
5use anyhow::Result;
6use get_size::GetSize;
7use serde_derive::Deserialize;
8use serde_derive::Serialize;
9use twenty_first::shared_math::b_field_element::BFieldElement;
10use twenty_first::shared_math::bfield_codec::BFieldCodec;
11use twenty_first::shared_math::digest::Digest;
12use twenty_first::util_types::algebraic_hasher::AlgebraicHasher;
13
14use crate::instruction::convert_labels;
15use crate::instruction::Instruction;
16use crate::instruction::LabelledInstruction;
17use crate::parser::parse;
18use crate::parser::to_labelled;
19
20/// A `Program` is a `Vec<Instruction>` that contains duplicate elements for instructions with a
21/// size of 2. This means that the index in the vector corresponds to the VM's
22/// `instruction_pointer`. These duplicate values should most often be skipped/ignored,
23/// e.g. when pretty-printing.
24#[derive(Debug, Clone, Default, PartialEq, Eq, GetSize, Serialize, Deserialize)]
25pub struct Program {
26    pub instructions: Vec<Instruction>,
27}
28
29impl Display for Program {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        let mut stream = self.instructions.iter();
32        while let Some(instruction) = stream.next() {
33            writeln!(f, "{instruction}")?;
34
35            // Skip duplicate placeholder used for aligning instructions and instruction_pointer in VM.
36            for _ in 1..instruction.size() {
37                stream.next();
38            }
39        }
40        Ok(())
41    }
42}
43
44impl BFieldCodec for Program {
45    fn decode(sequence: &[BFieldElement]) -> Result<Box<Self>> {
46        if sequence.is_empty() {
47            bail!("Sequence to decode must not be empty.");
48        }
49        let program_length = sequence[0].value() as usize;
50        let sequence = &sequence[1..];
51        if sequence.len() != program_length {
52            bail!(
53                "Sequence to decode must have length {program_length}, but has length {}.",
54                sequence.len()
55            );
56        }
57
58        let mut idx = 0;
59        let mut instructions = Vec::with_capacity(program_length);
60        while idx < program_length {
61            let opcode: u32 = match sequence[idx].value().try_into() {
62                Ok(opcode) => opcode,
63                Err(_) => bail!("Invalid opcode {} at index {idx}.", sequence[idx].value()),
64            };
65            let instruction: Instruction = opcode.try_into()?;
66            if !instruction.has_arg() {
67                instructions.push(instruction);
68            } else if instructions.len() + 1 >= program_length {
69                bail!("Missing argument for instruction {instruction} at index {idx}.");
70            } else {
71                let instruction = match instruction.change_arg(sequence[idx + 1]) {
72                    Some(instruction) => instruction,
73                    None => {
74                        bail!("Invalid argument for instruction {instruction} at index {idx}.")
75                    }
76                };
77                // Instructions with argument are recorded twice to align the `instruction_pointer`.
78                instructions.push(instruction);
79                instructions.push(instruction);
80            }
81            idx += instruction.size();
82        }
83
84        if idx != program_length {
85            bail!("Decoded program must have length {program_length}, but has length {idx}.",);
86        }
87        Ok(Box::new(Program { instructions }))
88    }
89
90    fn encode(&self) -> Vec<BFieldElement> {
91        let mut sequence = Vec::with_capacity(self.len_bwords() + 1);
92        sequence.push(BFieldElement::new(self.len_bwords() as u64));
93        sequence.extend(self.to_bwords());
94        sequence
95    }
96
97    fn static_length() -> Option<usize> {
98        None
99    }
100}
101
102/// An `InstructionIter` loops the instructions of a `Program` by skipping duplicate placeholders.
103pub struct InstructionIter {
104    cursor: Cursor<Vec<Instruction>>,
105}
106
107impl Iterator for InstructionIter {
108    type Item = Instruction;
109
110    fn next(&mut self) -> Option<Self::Item> {
111        let pos = self.cursor.position() as usize;
112        let instructions = self.cursor.get_ref();
113        let instruction = *instructions.get(pos)?;
114        self.cursor.set_position((pos + instruction.size()) as u64);
115
116        Some(instruction)
117    }
118}
119
120impl IntoIterator for Program {
121    type Item = Instruction;
122
123    type IntoIter = InstructionIter;
124
125    fn into_iter(self) -> Self::IntoIter {
126        let cursor = Cursor::new(self.instructions);
127        InstructionIter { cursor }
128    }
129}
130
131impl Program {
132    /// Create a `Program` from a slice of `Instruction`.
133    pub fn new(input: &[LabelledInstruction]) -> Self {
134        let instructions = convert_labels(input)
135            .iter()
136            .flat_map(|instr| vec![*instr; instr.size()])
137            .collect::<Vec<_>>();
138
139        Program { instructions }
140    }
141
142    /// Create a `Program` by parsing source code.
143    pub fn from_code(code: &str) -> Result<Self> {
144        parse(code)
145            .map(|program| Program::new(&to_labelled(&program)))
146            .map_err(|err| anyhow::anyhow!("{}", err))
147    }
148
149    /// Convert a `Program` to a `Vec<BFieldElement>`.
150    ///
151    /// Every single-word instruction is converted to a single word.
152    ///
153    /// Every double-word instruction is converted to two words.
154    pub fn to_bwords(&self) -> Vec<BFieldElement> {
155        self.clone()
156            .into_iter()
157            .flat_map(|instruction| {
158                let opcode = instruction.opcode_b();
159                if let Some(arg) = instruction.arg() {
160                    vec![opcode, arg]
161                } else {
162                    vec![opcode]
163                }
164            })
165            .collect()
166    }
167
168    /// The total length of the program as `BFieldElement`s. Double-word instructions contribute
169    /// two `BFieldElement`s.
170    pub fn len_bwords(&self) -> usize {
171        self.instructions.len()
172    }
173
174    pub fn is_empty(&self) -> bool {
175        self.instructions.is_empty()
176    }
177
178    /// Hash the program using the given `AlgebraicHasher`.
179    pub fn hash<H: AlgebraicHasher>(&self) -> Digest {
180        H::hash_varlen(&self.to_bwords())
181    }
182}
183
184#[cfg(test)]
185mod test {
186    use rand::thread_rng;
187    use rand::Rng;
188    use twenty_first::shared_math::tip5::Tip5;
189
190    use crate::parser::parser_tests::program_gen;
191
192    use super::*;
193
194    #[test]
195    fn random_program_encode_decode_equivalence() {
196        let mut rng = thread_rng();
197        for _ in 0..50 {
198            let program_len = rng.gen_range(20..420);
199            let source_code = program_gen(program_len);
200            let program = Program::from_code(&source_code).unwrap();
201
202            let encoded = program.encode();
203            let decoded = *Program::decode(&encoded).unwrap();
204
205            assert_eq!(program, decoded);
206        }
207    }
208
209    #[test]
210    fn decode_program_with_missing_argument_as_last_instruction() {
211        let program = Program::from_code("push 3 push 3 eq assert push 3").unwrap();
212        let program_length = program.len_bwords() as u64;
213        let encoded = program.encode();
214
215        let mut encoded = encoded[0..encoded.len() - 1].to_vec();
216        encoded[0] = BFieldElement::new(program_length - 1);
217
218        let err = Program::decode(&encoded).err().unwrap();
219        assert_eq!(
220            "Missing argument for instruction push 0 at index 6.",
221            err.to_string(),
222        );
223    }
224
225    #[test]
226    fn decode_program_with_length_mismatch() {
227        let program = Program::from_code("nop nop hash push 0 skiz end: halt call end").unwrap();
228        let program_length = program.len_bwords() as u64;
229        let mut encoded = program.encode();
230
231        encoded[0] = BFieldElement::new(program_length + 1);
232
233        let err = Program::decode(&encoded).err().unwrap();
234        assert_eq!(
235            "Sequence to decode must have length 10, but has length 9.",
236            err.to_string(),
237        );
238    }
239
240    #[test]
241    fn decode_program_from_empty_sequence() {
242        let encoded = vec![];
243        let err = Program::decode(&encoded).err().unwrap();
244        assert_eq!("Sequence to decode must not be empty.", err.to_string(),);
245    }
246
247    #[test]
248    fn hash_simple_program() {
249        let program = Program::from_code("halt").unwrap();
250        let digest = program.hash::<Tip5>();
251
252        let expected_digest = [
253            04843866011885844809,
254            16618866032559590857,
255            18247689143239181392,
256            07637465675240023996,
257            09104890367162237026,
258        ]
259        .map(BFieldElement::new);
260        let expected_digest = Digest::new(expected_digest);
261
262        assert_eq!(expected_digest, digest);
263    }
264
265    #[test]
266    fn empty_program_is_empty() {
267        let program = Program::from_code("").unwrap();
268        assert!(program.is_empty());
269    }
270}