1use crate::{
2 black_box::BlackBoxOp,
3 lengths::{ElementsFlattenedLength, FlattenedLength, SemanticLength, SemiFlattenedLength},
4};
5use acir_field::AcirField;
6use serde::{Deserialize, Serialize};
7
8pub type Label = usize;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
14#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
15pub enum MemoryAddress {
16 Direct(u32),
18 Relative(u32),
25}
26
27impl MemoryAddress {
28 pub fn direct(address: u32) -> Self {
30 MemoryAddress::Direct(address)
31 }
32
33 pub fn relative(offset: u32) -> Self {
35 MemoryAddress::Relative(offset)
36 }
37
38 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 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 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 pub fn offset(&self, amount: u32) -> Self {
81 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#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Hash)]
98pub enum HeapValueType {
99 Simple(BitSize),
101 Array { value_types: Vec<HeapValueType>, size: SemanticLength },
105 Vector { value_types: Vec<HeapValueType> },
109}
110
111impl HeapValueType {
112 pub fn all_simple(types: &[HeapValueType]) -> bool {
114 types.iter().all(|typ| matches!(typ, HeapValueType::Simple(_)))
115 }
116
117 pub fn field() -> HeapValueType {
119 HeapValueType::Simple(BitSize::Field)
120 }
121
122 pub fn flattened_size(&self) -> Option<FlattenedLength> {
126 match self {
127 HeapValueType::Simple(_) => Some(FlattenedLength(1)),
128 HeapValueType::Array { value_types, size } => {
129 let elements_flattened_size =
131 value_types.iter().map(|t| t.flattened_size()).sum::<Option<FlattenedLength>>();
132 elements_flattened_size.map(|elements_flattened_size| {
134 ElementsFlattenedLength::from(elements_flattened_size) * *size
135 })
136 }
137 HeapValueType::Vector { .. } => {
138 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, Hash)]
185#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
186pub struct HeapArray {
187 pub pointer: MemoryAddress,
191 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, Hash)]
209#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
210pub struct HeapVector {
211 pub pointer: MemoryAddress,
215 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#[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#[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 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 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, Hash)]
333#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
334pub enum ValueOrArray {
335 MemoryAddress(MemoryAddress),
340 HeapArray(HeapArray),
344 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 BinaryFieldOp {
373 destination: MemoryAddress,
374 op: BinaryFieldOp,
375 lhs: MemoryAddress,
376 rhs: MemoryAddress,
377 },
378 BinaryIntOp {
382 destination: MemoryAddress,
383 op: BinaryIntOp,
384 bit_size: IntegerBitSize,
385 lhs: MemoryAddress,
386 rhs: MemoryAddress,
387 },
388 Not { destination: MemoryAddress, source: MemoryAddress, bit_size: IntegerBitSize },
391 Cast { destination: MemoryAddress, source: MemoryAddress, bit_size: BitSize },
395 JumpIf { condition: MemoryAddress, location: Label },
398 Jump { location: Label },
400 CalldataCopy {
403 destination_address: MemoryAddress,
404 size_address: MemoryAddress,
405 offset_address: MemoryAddress,
406 },
407 Call { location: Label },
413 Const { destination: MemoryAddress, bit_size: BitSize, value: F },
415 IndirectConst { destination_pointer: MemoryAddress, bit_size: BitSize, value: F },
417 Return,
421 ForeignCall {
426 function: String,
429 destinations: Vec<ValueOrArray>,
435 destination_value_types: Vec<HeapValueType>,
437 inputs: Vec<ValueOrArray>,
439 input_value_types: Vec<HeapValueType>,
442 },
443 Mov { destination: MemoryAddress, source: MemoryAddress },
445 ConditionalMov {
451 destination: MemoryAddress,
452 source_a: MemoryAddress,
453 source_b: MemoryAddress,
454 condition: MemoryAddress,
455 },
456 Load { destination: MemoryAddress, source_pointer: MemoryAddress },
459 Store { destination_pointer: MemoryAddress, source: MemoryAddress },
462 BlackBox(BlackBoxOp),
465 Trap { revert_data: HeapVector },
467 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#[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 Div,
584 IntegerDiv,
586 Equals,
588 LessThan,
590 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#[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 Equals,
619 LessThan,
621 LessThanEquals,
623 And,
625 Or,
627 Xor,
629 Shl,
631 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]
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 let as_u32: u32 = int_size.into();
674 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 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 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]
711 fn test_bitsize_roundtrip() {
712 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 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 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 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 assert_eq!(BitSize::Field.to_u32::<FieldElement>(), 255);
767 }
768
769 #[test]
770 fn test_bitsize_try_from_u32_invalid() {
771 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 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}