brillig/
opcodes.rs

1use crate::{
2    black_box::BlackBoxOp,
3    lengths::{ElementsFlattenedLength, FlattenedLength, SemanticLength, SemiFlattenedLength},
4};
5use acir_field::AcirField;
6use serde::{Deserialize, Serialize};
7
8/// Represents a program location (instruction index) used as a jump target.
9pub type Label = usize;
10
11/// Represents an address in the VM's memory.
12/// Supports both direct and relative addressing.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
14#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
15pub enum MemoryAddress {
16    /// Specifies an exact index in the VM's memory.
17    Direct(u32),
18    /// Specifies an index relative to the stack pointer.
19    ///
20    /// It is resolved as the current stack pointer plus the offset stored here.
21    ///
22    /// The stack pointer is stored in memory slot 0, so this address is resolved
23    /// by reading that slot and adding the offset to get the final memory address.
24    Relative(u32),
25}
26
27impl MemoryAddress {
28    /// Create a `Direct` address.
29    pub fn direct(address: u32) -> Self {
30        MemoryAddress::Direct(address)
31    }
32
33    /// Create a `Relative` address.
34    pub fn relative(offset: u32) -> Self {
35        MemoryAddress::Relative(offset)
36    }
37
38    /// Return the index in a `Direct` address.
39    ///
40    /// Panics if it's `Relative`.
41    pub fn unwrap_direct(self) -> u32 {
42        match self {
43            MemoryAddress::Direct(address) => address,
44            MemoryAddress::Relative(_) => panic!("Expected direct memory address"),
45        }
46    }
47
48    /// Return the index in a `Relative` address.
49    ///
50    /// Panics if it's `Direct`.
51    pub fn unwrap_relative(self) -> u32 {
52        match self {
53            MemoryAddress::Direct(_) => panic!("Expected relative memory address"),
54            MemoryAddress::Relative(offset) => offset,
55        }
56    }
57
58    /// Return the index in the address.
59    pub fn to_u32(self) -> u32 {
60        match self {
61            MemoryAddress::Direct(address) => address,
62            MemoryAddress::Relative(offset) => offset,
63        }
64    }
65
66    pub fn is_relative(&self) -> bool {
67        match self {
68            MemoryAddress::Relative(_) => true,
69            MemoryAddress::Direct(_) => false,
70        }
71    }
72
73    pub fn is_direct(&self) -> bool {
74        !self.is_relative()
75    }
76
77    /// Offset a `Direct` address by `amount`.
78    ///
79    /// Panics if called on a `Relative` address.
80    pub fn offset(&self, amount: u32) -> Self {
81        // We disallow offsetting relatively addresses as this is not expected to be meaningful.
82        let address = self.unwrap_direct();
83        MemoryAddress::direct(address + amount)
84    }
85}
86
87impl std::fmt::Display for MemoryAddress {
88    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89        match self {
90            MemoryAddress::Direct(address) => write!(f, "@{address}"),
91            MemoryAddress::Relative(offset) => write!(f, "sp[{offset}]"),
92        }
93    }
94}
95
96/// Describes the memory layout for an array/vector element
97#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Hash)]
98pub enum HeapValueType {
99    /// A single field element is enough to represent the value with a given bit size.
100    Simple(BitSize),
101    /// The value read should be interpreted as a pointer to a [HeapArray], which
102    /// consists of a pointer to a slice of memory of size elements, and a
103    /// reference count, to avoid cloning arrays that are not shared.
104    Array { value_types: Vec<HeapValueType>, size: SemanticLength },
105    /// The value read should be interpreted as a pointer to a [HeapVector], which
106    /// consists of a pointer to a slice of memory, a number of elements in that
107    /// vector, and a reference count.
108    Vector { value_types: Vec<HeapValueType> },
109}
110
111impl HeapValueType {
112    /// Check that all types are `Simple`.
113    pub fn all_simple(types: &[HeapValueType]) -> bool {
114        types.iter().all(|typ| matches!(typ, HeapValueType::Simple(_)))
115    }
116
117    /// Create a `Simple` type to represent a `Field`.
118    pub fn field() -> HeapValueType {
119        HeapValueType::Simple(BitSize::Field)
120    }
121
122    /// Returns the total number of field elements required to represent this type in memory.
123    ///
124    /// Returns `None` for `Vector`, as their size is not statically known.
125    pub fn flattened_size(&self) -> Option<FlattenedLength> {
126        match self {
127            HeapValueType::Simple(_) => Some(FlattenedLength(1)),
128            HeapValueType::Array { value_types, size } => {
129                // This is the flattened length of a single entry in the array (all of `value_types`)
130                let elements_flattened_size =
131                    value_types.iter().map(|t| t.flattened_size()).sum::<Option<FlattenedLength>>();
132                // Next we multiply it by the size of the array
133                elements_flattened_size.map(|elements_flattened_size| {
134                    ElementsFlattenedLength::from(elements_flattened_size) * *size
135                })
136            }
137            HeapValueType::Vector { .. } => {
138                // Vectors are dynamic, so we cannot determine their size statically.
139                None
140            }
141        }
142    }
143}
144
145impl std::fmt::Display for HeapValueType {
146    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147        let write_types =
148            |f: &mut std::fmt::Formatter<'_>, value_types: &[HeapValueType]| -> std::fmt::Result {
149                if value_types.len() == 1 {
150                    write!(f, "{}", value_types[0])?;
151                } else {
152                    write!(f, "(")?;
153                    for (index, value_type) in value_types.iter().enumerate() {
154                        if index > 0 {
155                            write!(f, ", ")?;
156                        }
157                        write!(f, "{value_type}")?;
158                    }
159                    write!(f, ")")?;
160                }
161                Ok(())
162            };
163
164        match self {
165            HeapValueType::Simple(bit_size) => {
166                write!(f, "{bit_size}")
167            }
168            HeapValueType::Array { value_types, size } => {
169                write!(f, "[")?;
170                write_types(f, value_types)?;
171                write!(f, "; {size}")?;
172                write!(f, "]")
173            }
174            HeapValueType::Vector { value_types } => {
175                write!(f, "@[")?;
176                write_types(f, value_types)?;
177                write!(f, "]")
178            }
179        }
180    }
181}
182
183/// A fixed-sized array starting from a Brillig memory location.
184#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, Hash)]
185#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
186pub struct HeapArray {
187    /// Pointer to a memory address which hold the address to the start of the items in the array.
188    ///
189    /// That is to say, the address retrieved from the pointer doesn't need any more offsetting.
190    pub pointer: MemoryAddress,
191    /// Statically known size of the array.
192    pub size: SemiFlattenedLength,
193}
194
195impl Default for HeapArray {
196    fn default() -> Self {
197        Self { pointer: MemoryAddress::direct(0), size: SemiFlattenedLength(0) }
198    }
199}
200
201impl std::fmt::Display for HeapArray {
202    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203        write!(f, "[{}; {}]", self.pointer, self.size)
204    }
205}
206
207/// A memory-sized vector passed starting from a Brillig memory location and with a memory-held size.
208#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, Hash)]
209#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
210pub struct HeapVector {
211    /// Pointer to a memory address which hold the address to the start of the items in the vector.
212    ///
213    /// That is to say, the address retrieved from the pointer doesn't need any more offsetting.
214    pub pointer: MemoryAddress,
215    /// Address to a memory slot holding the semantic length of the vector.
216    pub size: MemoryAddress,
217}
218
219impl std::fmt::Display for HeapVector {
220    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
221        write!(f, "@[{}; {}]", self.pointer, self.size)
222    }
223}
224
225/// Represents the bit size of unsigned integer types in Brillig.
226///
227/// These correspond to the standard unsigned integer types, with U1 representing a boolean.
228#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)]
229#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
230pub enum IntegerBitSize {
231    U1,
232    U8,
233    U16,
234    U32,
235    U64,
236    U128,
237}
238
239impl From<IntegerBitSize> for u32 {
240    fn from(bit_size: IntegerBitSize) -> u32 {
241        match bit_size {
242            IntegerBitSize::U1 => 1,
243            IntegerBitSize::U8 => 8,
244            IntegerBitSize::U16 => 16,
245            IntegerBitSize::U32 => 32,
246            IntegerBitSize::U64 => 64,
247            IntegerBitSize::U128 => 128,
248        }
249    }
250}
251
252impl TryFrom<u32> for IntegerBitSize {
253    type Error = &'static str;
254
255    fn try_from(value: u32) -> Result<Self, Self::Error> {
256        match value {
257            1 => Ok(IntegerBitSize::U1),
258            8 => Ok(IntegerBitSize::U8),
259            16 => Ok(IntegerBitSize::U16),
260            32 => Ok(IntegerBitSize::U32),
261            64 => Ok(IntegerBitSize::U64),
262            128 => Ok(IntegerBitSize::U128),
263            _ => Err("Invalid bit size"),
264        }
265    }
266}
267
268impl std::fmt::Display for IntegerBitSize {
269    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
270        match self {
271            IntegerBitSize::U1 => write!(f, "bool"),
272            IntegerBitSize::U8 => write!(f, "u8"),
273            IntegerBitSize::U16 => write!(f, "u16"),
274            IntegerBitSize::U32 => write!(f, "u32"),
275            IntegerBitSize::U64 => write!(f, "u64"),
276            IntegerBitSize::U128 => write!(f, "u128"),
277        }
278    }
279}
280
281/// Represents the bit size of values in Brillig.
282///
283/// Values can either be field elements (whose size depends on the field being used)
284/// or fixed-size unsigned integers.
285#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)]
286#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
287pub enum BitSize {
288    Field,
289    Integer(IntegerBitSize),
290}
291
292impl BitSize {
293    /// Convert the bit size to a u32 value.
294    ///
295    /// For field elements, returns the maximum number of bits in the field.
296    /// For integers, returns the bit size of the integer type.
297    pub fn to_u32<F: AcirField>(self) -> u32 {
298        match self {
299            BitSize::Field => F::max_num_bits(),
300            BitSize::Integer(bit_size) => bit_size.into(),
301        }
302    }
303
304    /// Try to create a BitSize from a u32 value.
305    ///
306    /// If the value matches the field's maximum bit count, returns `BitSize::Field`.
307    /// Otherwise, attempts to interpret it as an integer bit size.
308    pub fn try_from_u32<F: AcirField>(value: u32) -> Result<Self, &'static str> {
309        if value == F::max_num_bits() {
310            Ok(BitSize::Field)
311        } else {
312            Ok(BitSize::Integer(IntegerBitSize::try_from(value)?))
313        }
314    }
315}
316
317impl std::fmt::Display for BitSize {
318    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
319        match self {
320            BitSize::Field => write!(f, "field"),
321            BitSize::Integer(bit_size) => write!(f, "{bit_size}"),
322        }
323    }
324}
325
326/// Lays out various ways an external foreign call's input and output data may be interpreted inside Brillig.
327/// This data can either be an individual value or memory.
328///
329/// While we are usually agnostic to how memory is passed within Brillig,
330/// this needs to be encoded somehow when dealing with an external system.
331/// For simplicity, the extra type information is given right in the `ForeignCall` instructions.
332#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, Hash)]
333#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
334pub enum ValueOrArray {
335    /// A single value to be passed to or from an external call.
336    /// It is an 'immediate' value - used without dereferencing.
337    /// For a foreign call input, the value is read directly from memory.
338    /// For a foreign call output, the value is written directly to memory.
339    MemoryAddress(MemoryAddress),
340    /// An array to be passed to or from an external call.
341    /// In the case of a foreign call input, the array is read from this Brillig memory location + `size` more cells.
342    /// In the case of a foreign call output, the array is written to this Brillig memory location with the `size` being here just as a sanity check for the write size.
343    HeapArray(HeapArray),
344    /// A vector to be passed to or from an external call.
345    /// In the case of a foreign call input, the vector is read from this Brillig memory location + as many cells as the second address indicates.
346    /// In the case of a foreign call output, the vector is written to this Brillig memory location as 'size' cells, with size being stored in the second address.
347    HeapVector(HeapVector),
348}
349
350impl std::fmt::Display for ValueOrArray {
351    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
352        match self {
353            ValueOrArray::MemoryAddress(memory_address) => {
354                write!(f, "{memory_address}")
355            }
356            ValueOrArray::HeapArray(heap_array) => {
357                write!(f, "{heap_array}")
358            }
359            ValueOrArray::HeapVector(heap_vector) => {
360                write!(f, "{heap_vector}")
361            }
362        }
363    }
364}
365
366#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
367#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
368pub enum BrilligOpcode<F> {
369    /// Takes the fields in addresses `lhs` and `rhs`,
370    /// performs the specified binary operation,
371    /// and stores the value in the `destination` address.
372    BinaryFieldOp {
373        destination: MemoryAddress,
374        op: BinaryFieldOp,
375        lhs: MemoryAddress,
376        rhs: MemoryAddress,
377    },
378    /// Takes the `bit_size` size integers in addresses `lhs` and `rhs`,
379    /// performs the specified binary operation,
380    /// and stores the value in the `destination` address.
381    BinaryIntOp {
382        destination: MemoryAddress,
383        op: BinaryIntOp,
384        bit_size: IntegerBitSize,
385        lhs: MemoryAddress,
386        rhs: MemoryAddress,
387    },
388    /// Takes the value from the `source` address, inverts it,
389    /// and stores the value in the `destination` address.
390    Not { destination: MemoryAddress, source: MemoryAddress, bit_size: IntegerBitSize },
391    /// Takes the value from the `source` address,
392    /// casts it into the type indicated by `bit_size`,
393    /// and stores the value in the `destination` address.
394    Cast { destination: MemoryAddress, source: MemoryAddress, bit_size: BitSize },
395    /// Sets the program counter to the value of `location`
396    /// if the value at `condition` is non-zero.
397    JumpIf { condition: MemoryAddress, location: Label },
398    /// Sets the program counter to the value of `location`.
399    Jump { location: Label },
400    /// Copies calldata after the `offset_address` with length indicated by `size_address`
401    /// to the specified `destination_address`.
402    CalldataCopy {
403        destination_address: MemoryAddress,
404        size_address: MemoryAddress,
405        offset_address: MemoryAddress,
406    },
407    /// Pushes the current program counter to the call stack as to set a return location.
408    /// Sets the program counter to the value of `location`.
409    ///
410    /// We don't support dynamic jumps or calls;
411    /// see <https://github.com/ethereum/aleth/issues/3404> for reasoning.
412    Call { location: Label },
413    /// Stores a constant `value` with a `bit_size` in the `destination` address.
414    Const { destination: MemoryAddress, bit_size: BitSize, value: F },
415    /// Reads the address from `destination_pointer`, then stores a constant `value` with a `bit_size` at that address.
416    IndirectConst { destination_pointer: MemoryAddress, bit_size: BitSize, value: F },
417    /// Pops the top element from the call stack, which represents the return location,
418    /// and sets the program counter to that value. This operation is used to return
419    /// from a function call.
420    Return,
421    /// Used to get data from an outside source.
422    ///
423    /// Also referred to as an Oracle, intended for things like state tree reads;
424    /// it shouldn't be confused with e.g. blockchain price oracles.
425    ForeignCall {
426        /// Interpreted by caller context, ie. this will have different meanings depending on
427        /// who the caller is.
428        function: String,
429        /// Destination addresses (may be single values or memory pointers).
430        ///
431        /// Output vectors are passed as a [ValueOrArray::MemoryAddress]. Since their size is not known up front,
432        /// we cannot allocate space for them on the heap. Instead, the VM is expected to write their data after
433        /// the current free memory pointer, and store the heap address into the destination.
434        destinations: Vec<ValueOrArray>,
435        /// Destination value types.
436        destination_value_types: Vec<HeapValueType>,
437        /// Input addresses (may be single values or memory pointers).
438        inputs: Vec<ValueOrArray>,
439        /// Input value types (for heap allocated structures indicates how to
440        /// retrieve the elements).
441        input_value_types: Vec<HeapValueType>,
442    },
443    /// Moves the content in the `source` address to the `destination` address.
444    Mov { destination: MemoryAddress, source: MemoryAddress },
445    /// If the value at `condition` is non-zero, moves the content in the `source_a`
446    /// address to the `destination` address, otherwise moves the content from the
447    /// `source_b` address instead.
448    ///
449    /// `destination = condition > 0 ? source_a : source_b`
450    ConditionalMov {
451        destination: MemoryAddress,
452        source_a: MemoryAddress,
453        source_b: MemoryAddress,
454        condition: MemoryAddress,
455    },
456    /// Reads the `source_pointer` to obtain a memory address, then retrieves the data
457    /// stored at that address and writes it to the `destination` address.
458    Load { destination: MemoryAddress, source_pointer: MemoryAddress },
459    /// Reads the `destination_pointer` to obtain a memory address, then stores the value
460    /// from the `source` address at that location.
461    Store { destination_pointer: MemoryAddress, source: MemoryAddress },
462    /// Native functions in the VM.
463    /// These are equivalent to the black box functions in ACIR.
464    BlackBox(BlackBoxOp),
465    /// Used to denote execution failure, halting the VM and returning data specified by a dynamically-sized vector.
466    Trap { revert_data: HeapVector },
467    /// Halts execution and returns data specified by a dynamically-sized vector.
468    Stop { return_data: HeapVector },
469}
470
471impl<F: std::fmt::Display> std::fmt::Display for BrilligOpcode<F> {
472    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
473        match self {
474            BrilligOpcode::BinaryFieldOp { destination, op, lhs, rhs } => {
475                write!(f, "{destination} = field {op} {lhs}, {rhs}")
476            }
477            BrilligOpcode::BinaryIntOp { destination, op, bit_size, lhs, rhs } => {
478                write!(f, "{destination} = {bit_size} {op} {lhs}, {rhs}")
479            }
480            BrilligOpcode::Not { destination, source, bit_size } => {
481                write!(f, "{destination} = {bit_size} not {source}")
482            }
483            BrilligOpcode::Cast { destination, source, bit_size } => {
484                write!(f, "{destination} = cast {source} to {bit_size}")
485            }
486            BrilligOpcode::JumpIf { condition, location } => {
487                write!(f, "jump if {condition} to {location}")
488            }
489            BrilligOpcode::Jump { location } => {
490                write!(f, "jump to {location}")
491            }
492            BrilligOpcode::CalldataCopy { destination_address, size_address, offset_address } => {
493                write!(
494                    f,
495                    "{destination_address} = calldata copy [{offset_address}; {size_address}]"
496                )
497            }
498            BrilligOpcode::Call { location } => {
499                write!(f, "call {location}")
500            }
501            BrilligOpcode::Const { destination, bit_size, value } => {
502                write!(f, "{destination} = const {bit_size} {value}")
503            }
504            BrilligOpcode::IndirectConst { destination_pointer, bit_size, value } => {
505                write!(f, "{destination_pointer} = indirect const {bit_size} {value}")
506            }
507            BrilligOpcode::Return => {
508                write!(f, "return")
509            }
510            BrilligOpcode::ForeignCall {
511                function,
512                destinations,
513                destination_value_types,
514                inputs,
515                input_value_types,
516            } => {
517                assert_eq!(destinations.len(), destination_value_types.len());
518
519                if !destinations.is_empty() {
520                    for (index, (destination, destination_value_type)) in
521                        destinations.iter().zip(destination_value_types).enumerate()
522                    {
523                        if index > 0 {
524                            write!(f, ", ")?;
525                        }
526                        write!(f, "{destination}: {destination_value_type}")?;
527                    }
528                    write!(f, " = ")?;
529                }
530
531                write!(f, "foreign call {function}(")?;
532
533                assert_eq!(inputs.len(), input_value_types.len());
534                for (index, (input, input_value_type)) in
535                    inputs.iter().zip(input_value_types).enumerate()
536                {
537                    if index > 0 {
538                        write!(f, ", ")?;
539                    }
540                    write!(f, "{input}: {input_value_type}")?;
541                }
542
543                write!(f, ")")?;
544                Ok(())
545            }
546            BrilligOpcode::Mov { destination, source } => {
547                write!(f, "{destination} = {source}")
548            }
549            BrilligOpcode::ConditionalMov { destination, source_a, source_b, condition } => {
550                write!(f, "{destination} = if {condition} then {source_a} else {source_b}")
551            }
552            BrilligOpcode::Load { destination, source_pointer } => {
553                write!(f, "{destination} = load {source_pointer}")
554            }
555            BrilligOpcode::Store { destination_pointer, source } => {
556                write!(f, "store {source} at {destination_pointer}")
557            }
558            BrilligOpcode::BlackBox(black_box_op) => {
559                write!(f, "{black_box_op}")
560            }
561            BrilligOpcode::Trap { revert_data } => {
562                write!(f, "trap {revert_data}")
563            }
564            BrilligOpcode::Stop { return_data } => {
565                write!(f, "stop {return_data}")
566            }
567        }
568    }
569}
570
571/// Binary operations on field elements.
572///
573/// Most operations work with field arithmetic, but some operations like
574/// `IntegerDiv` interpret the field elements as unsigned integers for the purpose
575/// of the operation (useful when field elements are used to represent integer values).
576#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
577#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
578pub enum BinaryFieldOp {
579    Add,
580    Sub,
581    Mul,
582    /// Field division (inverse multiplication in the field)
583    Div,
584    /// Unsigned integer division (treating field elements as unsigned integers)
585    IntegerDiv,
586    /// (==) Equal
587    Equals,
588    /// (<) Field less than
589    LessThan,
590    /// (<=) Field less or equal
591    LessThanEquals,
592}
593
594impl std::fmt::Display for BinaryFieldOp {
595    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
596        match self {
597            BinaryFieldOp::Add => write!(f, "add"),
598            BinaryFieldOp::Sub => write!(f, "sub"),
599            BinaryFieldOp::Mul => write!(f, "mul"),
600            BinaryFieldOp::Div => write!(f, "field_div"),
601            BinaryFieldOp::IntegerDiv => write!(f, "int_div"),
602            BinaryFieldOp::Equals => write!(f, "eq"),
603            BinaryFieldOp::LessThan => write!(f, "lt"),
604            BinaryFieldOp::LessThanEquals => write!(f, "lt_eq"),
605        }
606    }
607}
608
609/// Binary fixed-length integer expressions
610#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
611#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
612pub enum BinaryIntOp {
613    Add,
614    Sub,
615    Mul,
616    Div,
617    /// (==) Equal
618    Equals,
619    /// (<) Integer less than
620    LessThan,
621    /// (<=) Integer less or equal
622    LessThanEquals,
623    /// (&) Bitwise AND
624    And,
625    /// (|) Bitwise OR
626    Or,
627    /// (^) Bitwise XOR
628    Xor,
629    /// (<<) Shift left
630    Shl,
631    /// (>>) Shift right
632    Shr,
633}
634
635impl std::fmt::Display for BinaryIntOp {
636    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
637        match self {
638            BinaryIntOp::Add => write!(f, "add"),
639            BinaryIntOp::Sub => write!(f, "sub"),
640            BinaryIntOp::Mul => write!(f, "mul"),
641            BinaryIntOp::Div => write!(f, "div"),
642            BinaryIntOp::Equals => write!(f, "eq"),
643            BinaryIntOp::LessThan => write!(f, "lt"),
644            BinaryIntOp::LessThanEquals => write!(f, "lt_eq"),
645            BinaryIntOp::And => write!(f, "and"),
646            BinaryIntOp::Or => write!(f, "or"),
647            BinaryIntOp::Xor => write!(f, "xor"),
648            BinaryIntOp::Shl => write!(f, "shl"),
649            BinaryIntOp::Shr => write!(f, "shr"),
650        }
651    }
652}
653
654#[cfg(test)]
655mod tests {
656    use super::{BitSize, IntegerBitSize};
657    use acir_field::FieldElement;
658
659    /// Test that IntegerBitSize round trips correctly through From/TryFrom u32
660    #[test]
661    fn test_integer_bitsize_roundtrip() {
662        let integer_sizes = [
663            IntegerBitSize::U1,
664            IntegerBitSize::U8,
665            IntegerBitSize::U16,
666            IntegerBitSize::U32,
667            IntegerBitSize::U64,
668            IntegerBitSize::U128,
669        ];
670
671        for int_size in integer_sizes {
672            // Convert to u32 using From trait
673            let as_u32: u32 = int_size.into();
674            // Convert back using TryFrom trait
675            let roundtrip = IntegerBitSize::try_from(as_u32)
676                .expect("Should successfully convert back from u32");
677            assert_eq!(
678                int_size, roundtrip,
679                "IntegerBitSize::{int_size} should roundtrip through From<IntegerBitSize> for u32 and TryFrom<u32>"
680            );
681        }
682    }
683
684    #[test]
685    fn test_integer_bitsize_values() {
686        // Verify the actual u32 values returned by From trait
687        assert_eq!(u32::from(IntegerBitSize::U1), 1);
688        assert_eq!(u32::from(IntegerBitSize::U8), 8);
689        assert_eq!(u32::from(IntegerBitSize::U16), 16);
690        assert_eq!(u32::from(IntegerBitSize::U32), 32);
691        assert_eq!(u32::from(IntegerBitSize::U64), 64);
692        assert_eq!(u32::from(IntegerBitSize::U128), 128);
693    }
694
695    #[test]
696    fn test_integer_bitsize_try_from_invalid() {
697        // Test that invalid bit sizes return an error
698        assert!(IntegerBitSize::try_from(0).is_err());
699        assert!(IntegerBitSize::try_from(2).is_err());
700        assert!(IntegerBitSize::try_from(7).is_err());
701        assert!(IntegerBitSize::try_from(15).is_err());
702        assert!(IntegerBitSize::try_from(31).is_err());
703        assert!(IntegerBitSize::try_from(63).is_err());
704        assert!(IntegerBitSize::try_from(127).is_err());
705        assert!(IntegerBitSize::try_from(129).is_err());
706        assert!(IntegerBitSize::try_from(256).is_err());
707    }
708
709    /// Test that BitSize round-trips correctly through to_u32/try_from_u32
710    #[test]
711    fn test_bitsize_roundtrip() {
712        // Test all integer bit sizes
713        let integer_sizes = [
714            IntegerBitSize::U1,
715            IntegerBitSize::U8,
716            IntegerBitSize::U16,
717            IntegerBitSize::U32,
718            IntegerBitSize::U64,
719            IntegerBitSize::U128,
720        ];
721
722        for int_size in integer_sizes {
723            let bit_size = BitSize::Integer(int_size);
724            let as_u32 = bit_size.to_u32::<FieldElement>();
725            let roundtrip = BitSize::try_from_u32::<FieldElement>(as_u32)
726                .expect("Should successfully convert back from u32");
727            assert_eq!(
728                bit_size, roundtrip,
729                "BitSize::Integer({int_size}) should roundtrip through to_u32/try_from_u32"
730            );
731        }
732
733        // Test Field type
734        let field_bit_size = BitSize::Field;
735        let as_u32 = field_bit_size.to_u32::<FieldElement>();
736        let roundtrip = BitSize::try_from_u32::<FieldElement>(as_u32)
737            .expect("Should successfully convert Field back from u32");
738        assert_eq!(
739            field_bit_size, roundtrip,
740            "BitSize::Field should roundtrip through to_u32/try_from_u32"
741        );
742    }
743
744    #[test]
745    fn test_bitsize_to_u32_values_integers() {
746        // Verify the actual u32 values returned for integer types
747        assert_eq!(BitSize::Integer(IntegerBitSize::U1).to_u32::<FieldElement>(), 1);
748        assert_eq!(BitSize::Integer(IntegerBitSize::U8).to_u32::<FieldElement>(), 8);
749        assert_eq!(BitSize::Integer(IntegerBitSize::U16).to_u32::<FieldElement>(), 16);
750        assert_eq!(BitSize::Integer(IntegerBitSize::U32).to_u32::<FieldElement>(), 32);
751        assert_eq!(BitSize::Integer(IntegerBitSize::U64).to_u32::<FieldElement>(), 64);
752        assert_eq!(BitSize::Integer(IntegerBitSize::U128).to_u32::<FieldElement>(), 128);
753    }
754
755    #[test]
756    #[cfg(feature = "bn254")]
757    fn test_bitsize_to_u32_field_bn254() {
758        // Field type returns 254 bits for bn254
759        assert_eq!(BitSize::Field.to_u32::<FieldElement>(), 254);
760    }
761
762    #[test]
763    #[cfg(feature = "bls12_381")]
764    fn test_bitsize_to_u32_field_bls12_381() {
765        // Field type returns 255 bits for bls12_381
766        assert_eq!(BitSize::Field.to_u32::<FieldElement>(), 255);
767    }
768
769    #[test]
770    fn test_bitsize_try_from_u32_invalid() {
771        // Test that invalid bit sizes return an error
772        assert!(BitSize::try_from_u32::<FieldElement>(2).is_err());
773        assert!(BitSize::try_from_u32::<FieldElement>(7).is_err());
774        assert!(BitSize::try_from_u32::<FieldElement>(0).is_err());
775        assert!(BitSize::try_from_u32::<FieldElement>(256).is_err());
776    }
777}
778
779#[cfg(feature = "arb")]
780mod prop_tests {
781    use proptest::arbitrary::Arbitrary;
782    use proptest::prelude::*;
783
784    use crate::lengths::SemanticLength;
785
786    use super::{BitSize, HeapValueType};
787
788    // Need to define recursive strategy for `HeapValueType`
789    impl Arbitrary for HeapValueType {
790        type Parameters = ();
791        type Strategy = BoxedStrategy<Self>;
792
793        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
794            let leaf = any::<BitSize>().prop_map(HeapValueType::Simple);
795            leaf.prop_recursive(2, 3, 2, |inner| {
796                prop_oneof![
797                    (prop::collection::vec(inner.clone(), 1..3), any::<u32>()).prop_map(
798                        |(value_types, size)| {
799                            HeapValueType::Array { value_types, size: SemanticLength(size) }
800                        }
801                    ),
802                    (prop::collection::vec(inner, 1..3))
803                        .prop_map(|value_types| { HeapValueType::Vector { value_types } }),
804                ]
805            })
806            .boxed()
807        }
808    }
809}