Skip to main content

snarkvm_synthesizer_program/logic/instruction/operation/
serialize.rs

1// Copyright (c) 2019-2026 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use crate::{Opcode, Operand, RegistersCircuit, RegistersTrait, StackTrait};
17use console::{
18    network::prelude::*,
19    program::{ArrayType, Identifier, LiteralType, Locator, Plaintext, PlaintextType, Register, RegisterType, Value},
20};
21
22/// Serializes the bits of the input.
23pub type SerializeBits<N> = SerializeInstruction<N, { SerializeVariant::ToBits as u8 }>;
24/// Serializes the raw bits of the input.
25pub type SerializeBitsRaw<N> = SerializeInstruction<N, { SerializeVariant::ToBitsRaw as u8 }>;
26
27/// The serialize variant.
28#[derive(Debug, Clone, Eq, PartialEq)]
29pub enum SerializeVariant {
30    ToBits,
31    ToBitsRaw,
32}
33
34impl SerializeVariant {
35    // Returns the opcode associated with the variant.
36    pub const fn opcode(variant: u8) -> &'static str {
37        match variant {
38            0 => "serialize.bits",
39            1 => "serialize.bits.raw",
40            _ => panic!("Invalid 'serialize' instruction opcode"),
41        }
42    }
43}
44
45/// Checks that the number of operands is correct.
46fn check_number_of_operands(variant: u8, num_operands: usize) -> Result<()> {
47    if num_operands != 1 {
48        bail!("Instruction '{}' expects 1 operand, found {num_operands} operands", SerializeVariant::opcode(variant))
49    }
50    Ok(())
51}
52
53/// Checks that the operand type is valid.
54fn check_operand_type_is_valid(variant: u8, operand_type: &PlaintextType<impl Network>) -> Result<()> {
55    // A helper function to check a literal type.
56    fn check_literal_type(literal_type: &LiteralType) -> Result<()> {
57        match literal_type {
58            LiteralType::Address
59            | LiteralType::Boolean
60            | LiteralType::Field
61            | LiteralType::Group
62            | LiteralType::I8
63            | LiteralType::I16
64            | LiteralType::I32
65            | LiteralType::I64
66            | LiteralType::I128
67            | LiteralType::U8
68            | LiteralType::U16
69            | LiteralType::U32
70            | LiteralType::U64
71            | LiteralType::U128
72            | LiteralType::Scalar
73            | LiteralType::Identifier => Ok(()),
74            _ => bail!("Invalid literal type '{literal_type}' for 'serialize' instruction"),
75        }
76    }
77
78    match operand_type {
79        PlaintextType::Literal(literal_type) => check_literal_type(literal_type),
80        PlaintextType::Array(array_type) => match array_type.base_element_type() {
81            PlaintextType::Literal(literal_type) => check_literal_type(literal_type),
82            _ => bail!("Invalid element type '{array_type}' for 'serialize' instruction"),
83        },
84        _ => bail!("Instruction '{}' cannot take type '{operand_type}' as input", SerializeVariant::opcode(variant)),
85    }
86}
87
88/// Check that the destination type is valid.
89fn check_destination_type_is_valid(variant: u8, destination_type: &ArrayType<impl Network>) -> Result<()> {
90    match (variant, destination_type) {
91        (0 | 1, array_type) if array_type.is_bit_array() => Ok(()),
92        _ => {
93            bail!("Instruction '{}' cannot output type '{destination_type}'", SerializeVariant::opcode(variant))
94        }
95    }
96}
97
98/// Serialize the operand into the declared type.
99#[derive(Clone, PartialEq, Eq, Hash)]
100pub struct SerializeInstruction<N: Network, const VARIANT: u8> {
101    /// The operand as `input`.
102    operands: Vec<Operand<N>>,
103    /// The operand type.
104    operand_type: PlaintextType<N>,
105    /// The destination register.
106    destination: Register<N>,
107    /// The destination register type.
108    destination_type: ArrayType<N>,
109}
110
111impl<N: Network, const VARIANT: u8> SerializeInstruction<N, VARIANT> {
112    /// Initializes a new `serialize` instruction.
113    pub fn new(
114        operands: Vec<Operand<N>>,
115        operand_type: PlaintextType<N>,
116        destination: Register<N>,
117        destination_type: ArrayType<N>,
118    ) -> Result<Self> {
119        // Sanity check the number of operands.
120        check_number_of_operands(VARIANT, operands.len())?;
121        // Ensure that the operand type is valid.
122        check_operand_type_is_valid(VARIANT, &operand_type)?;
123        // Sanity check the destination type.
124        check_destination_type_is_valid(VARIANT, &destination_type)?;
125        // Return the instruction.
126        Ok(Self { operands, operand_type, destination, destination_type })
127    }
128
129    /// Returns the opcode.
130    pub const fn opcode() -> Opcode {
131        Opcode::Serialize(SerializeVariant::opcode(VARIANT))
132    }
133
134    /// Returns the operands in the operation.
135    pub fn operands(&self) -> &[Operand<N>] {
136        // Sanity check that the operands is the correct length.
137        if cfg!(debug_assertions) {
138            check_number_of_operands(VARIANT, self.operands.len()).unwrap();
139            check_operand_type_is_valid(VARIANT, &self.operand_type).unwrap();
140            check_destination_type_is_valid(VARIANT, &self.destination_type).unwrap();
141        }
142        // Return the operand.
143        &self.operands
144    }
145
146    /// Returns the operand type.
147    pub const fn operand_type(&self) -> &PlaintextType<N> {
148        &self.operand_type
149    }
150
151    /// Returns the destination register.
152    #[inline]
153    pub fn destinations(&self) -> Vec<Register<N>> {
154        vec![self.destination.clone()]
155    }
156
157    /// Returns the destination register type.
158    #[inline]
159    pub const fn destination_type(&self) -> &ArrayType<N> {
160        &self.destination_type
161    }
162
163    /// Returns whether this instruction refers to an external struct.
164    #[inline]
165    pub fn contains_external_struct(&self) -> bool {
166        self.operand_type.contains_external_struct()
167    }
168}
169
170/// Evaluate a `serialize` operation.
171///
172/// This allows running `serialize` without the machinery of stacks and registers.
173/// This is necessary for the Leo interpreter.
174pub fn evaluate_serialize<N: Network>(
175    variant: SerializeVariant,
176    input: &Value<N>,
177    destination_type: &ArrayType<N>,
178) -> Result<Value<N>> {
179    evaluate_serialize_internal(variant as u8, input, destination_type)
180}
181
182fn evaluate_serialize_internal<N: Network>(
183    variant: u8,
184    input: &Value<N>,
185    destination_type: &ArrayType<N>,
186) -> Result<Value<N>> {
187    match (variant, destination_type) {
188        (0, array_type) if array_type.is_bit_array() => {
189            // Get the desired length of the array.
190            let length = **array_type.length();
191            // Serialize the input to bits.
192            let bits = input.to_bits_le();
193            // Return the bits as a plaintext array.
194            Ok(Value::Plaintext(Plaintext::from_bit_array(bits, length)?))
195        }
196        (1, array_type) if array_type.is_bit_array() => {
197            // Get the desired length of the array.
198            let length = **array_type.length();
199            // Serialize the input to raw bits.
200            let bits = input.to_bits_raw_le();
201            // Return the bits as a plaintext array.
202            Ok(Value::Plaintext(Plaintext::from_bit_array(bits, length)?))
203        }
204        _ => bail!(
205            "Invalid destination type '{}' for instruction '{}'",
206            destination_type,
207            SerializeVariant::opcode(variant)
208        ),
209    }
210}
211
212impl<N: Network, const VARIANT: u8> SerializeInstruction<N, VARIANT> {
213    /// Evaluates the instruction.
214    pub fn evaluate(&self, stack: &impl StackTrait<N>, registers: &mut impl RegistersTrait<N>) -> Result<()> {
215        // Ensure the number of operands is correct.
216        check_number_of_operands(VARIANT, self.operands.len())?;
217        // Ensure that the operand type is valid.
218        check_operand_type_is_valid(VARIANT, &self.operand_type)?;
219        // Ensure the destination type is valid.
220        check_destination_type_is_valid(VARIANT, &self.destination_type)?;
221
222        // Load the operand.
223        let input = registers.load(stack, &self.operands[0])?;
224
225        let output = evaluate_serialize_internal(VARIANT, &input, &self.destination_type)?;
226
227        // Store the output.
228        registers.store(stack, &self.destination, output)
229    }
230
231    /// Executes the instruction.
232    pub fn execute<A: circuit::Aleo<Network = N>>(
233        &self,
234        stack: &impl StackTrait<N>,
235        registers: &mut impl RegistersCircuit<N, A>,
236    ) -> Result<()> {
237        use crate::circuit::traits::{ToBits, ToBitsRaw};
238
239        // Ensure the number of operands is correct.
240        check_number_of_operands(VARIANT, self.operands.len())?;
241        // Ensure that the operand type is valid.
242        check_operand_type_is_valid(VARIANT, &self.operand_type)?;
243        // Ensure the destination type is valid.
244        check_destination_type_is_valid(VARIANT, &self.destination_type)?;
245
246        // Load the operand.
247        let input = registers.load_circuit(stack, &self.operands[0])?;
248
249        let output = match (VARIANT, &self.destination_type) {
250            (0, array_type) if array_type.is_bit_array() => {
251                // Get the desired length of the array.
252                let length = **array_type.length();
253                // Serialize the input to bits.
254                let bits = input.to_bits_le();
255                // Return the bits as a plaintext array.
256                circuit::Value::Plaintext(circuit::Plaintext::from_bit_array(bits, length)?)
257            }
258            (1, array_type) if array_type.is_bit_array() => {
259                // Get the desired length of the array.
260                let length = **array_type.length();
261                // Serialize the input to raw bits.
262                let bits = input.to_bits_raw_le();
263                // Return the bits as a plaintext array.
264                circuit::Value::Plaintext(circuit::Plaintext::from_bit_array(bits, length)?)
265            }
266            _ => bail!("Invalid destination type '{}' for instruction '{}'", &self.destination_type, Self::opcode(),),
267        };
268
269        // Store the output.
270        registers.store_circuit(stack, &self.destination, output)
271    }
272
273    /// Finalizes the instruction.
274    #[inline]
275    pub fn finalize(&self, stack: &impl StackTrait<N>, registers: &mut impl RegistersTrait<N>) -> Result<()> {
276        self.evaluate(stack, registers)
277    }
278
279    /// Returns the output type from the given program and input types.
280    pub fn output_types(
281        &self,
282        stack: &impl StackTrait<N>,
283        input_types: &[RegisterType<N>],
284    ) -> Result<Vec<RegisterType<N>>> {
285        // Ensure the number of operands is correct.
286        check_number_of_operands(VARIANT, self.operands.len())?;
287        // Ensure the operand type is valid.
288        check_operand_type_is_valid(VARIANT, &self.operand_type)?;
289        // Ensure the destination type is valid.
290        check_destination_type_is_valid(VARIANT, &self.destination_type)?;
291
292        // Check that the input type matches the operand type.
293        ensure!(input_types.len() == 1, "Expected exactly one input type");
294        match &input_types[0] {
295            RegisterType::Plaintext(plaintext_type) => {
296                ensure!(
297                    plaintext_type == &self.operand_type,
298                    "Input type {} does not match operand type {}",
299                    input_types[0],
300                    self.operand_type
301                )
302            }
303            type_ => bail!("Input type {type_} does not match operand type {}", self.operand_type),
304        }
305
306        // A helper to get a struct declaration.
307        let get_struct = |identifier: &Identifier<N>| stack.program().get_struct(identifier).cloned();
308
309        // A helper to get an external struct declaration.
310        let get_external_struct = |locator: &Locator<N>| {
311            stack.get_external_stack(locator.program_id())?.program().get_struct(locator.resource()).cloned()
312        };
313
314        // Get the size in bits of the operand.
315        let size_in_bits = match VARIANT {
316            0 => self.operand_type.size_in_bits(&get_struct, &get_external_struct)?,
317            1 => self.operand_type.size_in_bits_raw(&get_struct, &get_external_struct)?,
318            variant => bail!("Invalid `serialize` variant '{variant}'"),
319        };
320
321        // Check that the number of bits of the operand matches the destination.
322        ensure!(
323            size_in_bits == **self.destination_type.length() as usize,
324            "The number of bits of the operand '{size_in_bits}' does not match the destination '{}'",
325            **self.destination_type.length()
326        );
327
328        Ok(vec![RegisterType::Plaintext(PlaintextType::Array(self.destination_type.clone()))])
329    }
330}
331
332impl<N: Network, const VARIANT: u8> Parser for SerializeInstruction<N, VARIANT> {
333    /// Parses a string into an operation.
334    fn parse(string: &str) -> ParserResult<Self> {
335        /// Parse the operands from the string.
336        fn parse_operands<N: Network>(string: &str, num_operands: usize) -> ParserResult<Vec<Operand<N>>> {
337            let mut operands = Vec::with_capacity(num_operands);
338            let mut string = string;
339
340            for _ in 0..num_operands {
341                // Parse the whitespace from the string.
342                let (next_string, _) = Sanitizer::parse_whitespaces(string)?;
343                // Parse the operand from the string.
344                let (next_string, operand) = Operand::parse(next_string)?;
345                // Update the string.
346                string = next_string;
347                // Push the operand.
348                operands.push(operand);
349            }
350
351            Ok((string, operands))
352        }
353
354        // Parse the opcode from the string.
355        let (string, _) = tag(*Self::opcode())(string)?;
356        // Parse the operands from the string.
357        let (string, operands) = parse_operands(string, 1)?;
358
359        // Parse the whitespace from the string.
360        let (string, _) = Sanitizer::parse_whitespaces(string)?;
361        // Parse the "(" from the string.
362        let (string, _) = tag("(")(string)?;
363        // Parse the whitespace from the string.
364        let (string, _) = Sanitizer::parse_whitespaces(string)?;
365        // Parse the operand type from the string.
366        let (string, operand_type) = PlaintextType::parse(string)?;
367        // Parse the ")" from the string.
368        let (string, _) = tag(")")(string)?;
369
370        // Parse the whitespace from the string.
371        let (string, _) = Sanitizer::parse_whitespaces(string)?;
372        // Parse the "into" from the string.
373        let (string, _) = tag("into")(string)?;
374        // Parse the whitespace from the string.
375        let (string, _) = Sanitizer::parse_whitespaces(string)?;
376        // Parse the destination register from the string.
377        let (string, destination) = Register::parse(string)?;
378
379        // Parse the whitespace from the string.
380        let (string, _) = Sanitizer::parse_whitespaces(string)?;
381        // Parse the "(" from the string.
382        let (string, _) = tag("(")(string)?;
383        // Parse the whitespace from the string.
384        let (string, _) = Sanitizer::parse_whitespaces(string)?;
385        // Parse the destination register type from the string.
386        let (string, destination_type) = ArrayType::parse(string)?;
387        // Parse the ")" from the string.
388        let (string, _) = tag(")")(string)?;
389
390        // Construct the instruction, checking for errors.
391        match Self::new(operands, operand_type, destination, destination_type) {
392            Ok(instruction) => Ok((string, instruction)),
393            Err(e) => map_res(fail, |_: ParserResult<Self>| {
394                Err(error(format!("Failed to parse '{}' instruction: {e}", Self::opcode())))
395            })(string),
396        }
397    }
398}
399
400impl<N: Network, const VARIANT: u8> FromStr for SerializeInstruction<N, VARIANT> {
401    type Err = Error;
402
403    /// Parses a string into an operation.
404    fn from_str(string: &str) -> Result<Self> {
405        match Self::parse(string) {
406            Ok((remainder, object)) => {
407                // Ensure the remainder is empty.
408                ensure!(remainder.is_empty(), "Failed to parse string. Found invalid character in: \"{remainder}\"");
409                // Return the object.
410                Ok(object)
411            }
412            Err(error) => bail!("Failed to parse string. {error}"),
413        }
414    }
415}
416
417impl<N: Network, const VARIANT: u8> Debug for SerializeInstruction<N, VARIANT> {
418    /// Prints the operation as a string.
419    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
420        Display::fmt(self, f)
421    }
422}
423
424impl<N: Network, const VARIANT: u8> Display for SerializeInstruction<N, VARIANT> {
425    /// Prints the operation to a string.
426    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
427        write!(f, "{} ", Self::opcode())?;
428        self.operands.iter().try_for_each(|operand| write!(f, "{operand} "))?;
429        write!(f, " ({}) into {} ({})", self.operand_type, self.destination, self.destination_type)
430    }
431}
432
433impl<N: Network, const VARIANT: u8> FromBytes for SerializeInstruction<N, VARIANT> {
434    /// Reads the operation from a buffer.
435    fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
436        // Read the operand.
437        let operand = Operand::read_le(&mut reader)?;
438        // Read the operand type.
439        let operand_type = PlaintextType::read_le(&mut reader)?;
440        // Read the destination register.
441        let destination = Register::read_le(&mut reader)?;
442        // Read the destination register type.
443        let destination_type = ArrayType::read_le(&mut reader)?;
444        // Return the operation.
445        match Self::new(vec![operand], operand_type, destination, destination_type) {
446            Ok(instruction) => Ok(instruction),
447            Err(e) => Err(error(format!("Failed to read '{}' instruction: {e}", Self::opcode()))),
448        }
449    }
450}
451
452impl<N: Network, const VARIANT: u8> ToBytes for SerializeInstruction<N, VARIANT> {
453    /// Writes the operation to a buffer.
454    fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
455        // Write the operands.
456        self.operands.iter().try_for_each(|operand| operand.write_le(&mut writer))?;
457        // Write the operand type.
458        self.operand_type.write_le(&mut writer)?;
459        // Write the destination register.
460        self.destination.write_le(&mut writer)?;
461        // Write the destination register type.
462        self.destination_type.write_le(&mut writer)
463    }
464}
465
466#[cfg(test)]
467mod tests {
468    use super::*;
469    use console::{network::MainnetV0, types::U32};
470
471    type CurrentNetwork = MainnetV0;
472
473    /// **Attention**: When changing this, also update in `tests/instruction/serialize.rs`.
474    fn valid_source_types<N: Network>() -> &'static [PlaintextType<N>] {
475        &[
476            PlaintextType::Literal(LiteralType::Address),
477            PlaintextType::Literal(LiteralType::Field),
478            PlaintextType::Literal(LiteralType::Group),
479            PlaintextType::Literal(LiteralType::I8),
480            PlaintextType::Literal(LiteralType::I16),
481            PlaintextType::Literal(LiteralType::I32),
482            PlaintextType::Literal(LiteralType::I128),
483            PlaintextType::Literal(LiteralType::I64),
484            PlaintextType::Literal(LiteralType::U8),
485            PlaintextType::Literal(LiteralType::U16),
486            PlaintextType::Literal(LiteralType::U32),
487            PlaintextType::Literal(LiteralType::U64),
488            PlaintextType::Literal(LiteralType::U128),
489            PlaintextType::Literal(LiteralType::Scalar),
490            PlaintextType::Literal(LiteralType::Identifier),
491        ]
492    }
493
494    /// Randomly sample a destination type.
495    fn sample_destination_type<N: Network, const VARIANT: u8>(rng: &mut TestRng) -> ArrayType<N> {
496        // Generate a random array length between 1 and N::LATEST_MAX_ARRAY_ELEMENTS().
497        let array_length = 1 + (u32::rand(rng) % u32::try_from(N::LATEST_MAX_ARRAY_ELEMENTS()).unwrap());
498        match VARIANT {
499            0 | 1 => {
500                ArrayType::new(PlaintextType::Literal(LiteralType::Boolean), vec![U32::new(array_length)]).unwrap()
501            }
502            _ => panic!("Invalid variant"),
503        }
504    }
505
506    fn run_parser_test<const VARIANT: u8>(rng: &mut TestRng) {
507        for source_type in valid_source_types() {
508            {
509                let opcode = SerializeVariant::opcode(VARIANT);
510                let destination_type = sample_destination_type::<CurrentNetwork, VARIANT>(rng);
511                let instruction = format!("{opcode} r0 ({source_type}) into r1 ({destination_type})");
512                println!("Parsing instruction: '{instruction}'");
513
514                let (string, serialize) = SerializeInstruction::<CurrentNetwork, VARIANT>::parse(&instruction).unwrap();
515                assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
516                assert_eq!(serialize.operands.len(), 1, "The number of operands is incorrect");
517                assert_eq!(
518                    serialize.operands[0],
519                    Operand::Register(Register::Locator(0)),
520                    "The first operand is incorrect"
521                );
522                assert_eq!(&serialize.operand_type, source_type, "The operand type is incorrect");
523                assert_eq!(serialize.destination, Register::Locator(1), "The destination register is incorrect");
524                assert_eq!(&serialize.destination_type, &destination_type, "The destination type is incorrect");
525            }
526        }
527    }
528
529    #[test]
530    fn test_parse() {
531        // Initialize an RNG.
532        let rng = &mut TestRng::default();
533
534        // Run the parser test for each variant.
535        run_parser_test::<{ SerializeVariant::ToBits as u8 }>(rng);
536        run_parser_test::<{ SerializeVariant::ToBitsRaw as u8 }>(rng);
537
538        SerializeBitsRaw::<CurrentNetwork>::from_str("serialize.bits.raw r0 (boolean) into r1 ([boolean; 1u32])")
539            .unwrap();
540    }
541}