1use std::mem;
8
9use binius_compute::GlobalAllocator;
10use binius_core::{
11 ValueTable, Word, WordSource,
12 error::{ChipName, OperandFault},
13 eval_operand,
14 m4::{ChipCall, ConstraintSystemM4, EmbeddedConstraintSystem, WitnessM4},
15};
16use binius_utils::checked_arithmetics::log2_ceil_usize;
17
18use crate::{
19 Circuit, CircuitBuilder,
20 artifact::witness::{PopulateError, WitnessFiller},
21 eval_form::BatchPopulateError,
22 ir::{Wire, hints::Hint},
23};
24
25pub struct EmbeddedCircuit {
41 pub circuit: Circuit,
43 pub chip_calls: Vec<ChipCall>,
45}
46
47pub struct CircuitM4 {
52 pub main: EmbeddedCircuit,
54 pub chips: Vec<(EmbeddedCircuit, usize)>,
64}
65
66impl From<Circuit> for CircuitM4 {
67 fn from(circuit: Circuit) -> Self {
69 Self {
70 main: EmbeddedCircuit {
71 circuit,
72 chip_calls: Vec::new(),
73 },
74 chips: Vec::new(),
75 }
76 }
77}
78
79impl CircuitM4 {
80 pub fn validate(&self) -> Result<(), CircuitM4Error> {
98 let n_chips = self.chips.len();
99
100 self.validate_calls(None, &self.main)?;
101 for (chip_index, (chip, _)) in self.chips.iter().enumerate() {
102 self.validate_calls(Some(chip_index), chip)?;
103 for call in &chip.chip_calls {
104 if call.chip_id <= chip_index {
105 return Err(CircuitM4Error::CallOutOfOrder {
106 chip_index,
107 callee: call.chip_id,
108 });
109 }
110 }
111 }
112
113 let mut n_calls = vec![0usize; n_chips];
117 for (call_index, call) in self.main.chip_calls.iter().enumerate() {
118 Self::check_call_instance(None, call_index, call, &n_calls)?;
119 n_calls[call.chip_id] += 1;
120 }
121 for (chip_index, (chip, n_active)) in self.chips.iter().enumerate() {
122 if n_calls[chip_index] != *n_active {
123 return Err(CircuitM4Error::WrongActiveInstanceCount {
124 chip_index,
125 declared: *n_active,
126 actual: n_calls[chip_index],
127 });
128 }
129 if *n_active == 0 {
130 return Err(CircuitM4Error::NeverCalled { chip_index });
131 }
132
133 for (call_index, call) in chip.chip_calls.iter().enumerate() {
142 Self::check_call_instance(Some(chip_index), call_index, call, &n_calls)?;
143 n_calls[call.chip_id] = n_calls[call.chip_id].checked_add(*n_active).ok_or(
144 CircuitM4Error::TooManyInstances {
145 chip_index: call.chip_id,
146 },
147 )?;
148 }
149 }
150
151 Ok(())
152 }
153
154 const fn check_call_instance(
156 chip_index: Option<usize>,
157 call_index: usize,
158 call: &ChipCall,
159 n_calls: &[usize],
160 ) -> Result<(), CircuitM4Error> {
161 let expected = n_calls[call.chip_id];
162 if call.first_instance != expected {
163 return Err(CircuitM4Error::WrongCallInstance {
164 chip_index,
165 call_index,
166 first_instance: call.first_instance,
167 expected,
168 });
169 }
170 Ok(())
171 }
172
173 fn validate_calls(
180 &self,
181 chip_index: Option<usize>,
182 caller: &EmbeddedCircuit,
183 ) -> Result<(), CircuitM4Error> {
184 let n_chips = self.chips.len();
185 let cs = caller.circuit.constraint_system();
186 for (call_index, call) in caller.chip_calls.iter().enumerate() {
187 if call.chip_id >= n_chips {
188 return Err(CircuitM4Error::OutOfRangeChipId {
189 chip_index,
190 chip_id: call.chip_id,
191 n_chips,
192 });
193 }
194 let n_inout = self.chips[call.chip_id]
195 .0
196 .circuit
197 .constraint_system()
198 .n_inout;
199 if call.inout.len() > n_inout {
200 return Err(CircuitM4Error::WrongCallArity {
201 chip_index,
202 call_index,
203 chip_id: call.chip_id,
204 arity: call.inout.len(),
205 n_inout,
206 });
207 }
208 for (operand_index, operand) in call.inout.iter().enumerate() {
209 if let Some(source) = cs.operand_fault(operand) {
210 return Err(CircuitM4Error::CallOperand {
211 chip_index,
212 call_index,
213 operand_index,
214 source,
215 });
216 }
217 }
218 }
219 Ok(())
220 }
221
222 pub fn recompute_instances(&mut self) {
243 let mut n_calls = vec![0usize; self.chips.len()];
244 for call in &mut self.main.chip_calls {
245 call.first_instance = n_calls[call.chip_id];
246 n_calls[call.chip_id] += 1;
247 }
248 for chip_index in 0..self.chips.len() {
251 let n_active = n_calls[chip_index];
252 self.chips[chip_index].1 = n_active;
253
254 for call in &mut self.chips[chip_index].0.chip_calls {
257 call.first_instance = n_calls[call.chip_id];
258 n_calls[call.chip_id] = n_calls[call.chip_id].saturating_add(n_active);
259 }
260 }
261 }
262
263 pub fn to_constraint_system(&self) -> ConstraintSystemM4 {
268 let lower = |chip: &EmbeddedCircuit| EmbeddedConstraintSystem {
269 cs: chip.circuit.constraint_system().clone(),
270 chip_calls: chip.chip_calls.clone(),
271 };
272 ConstraintSystemM4 {
273 main: lower(&self.main),
274 chips: self
275 .chips
276 .iter()
277 .map(|(chip, n_active)| (lower(chip), *n_active))
278 .collect(),
279 }
280 }
281
282 pub fn generate_witness<F>(&self, fill_main: F) -> Result<WitnessM4, PopulateM4Error>
295 where
296 F: FnOnce(&mut WitnessFiller<'_>),
297 {
298 let mut main_witness_filler = self.main.circuit.new_witness_filler();
299 fill_main(&mut main_witness_filler);
300 self.main
301 .circuit
302 .populate_wire_witness(&mut main_witness_filler)?;
303
304 let main_values = main_witness_filler.into_value_vec();
305
306 let mut pending = self
309 .chips
310 .iter()
311 .map(|(_, n_active)| vec![Vec::new(); *n_active])
312 .collect::<Vec<_>>();
313 for call in &self.main.chip_calls {
314 pending[call.chip_id][call.first_instance] = eval_call(&main_values, call);
315 }
316
317 let mut tables = Vec::<ValueTable>::with_capacity(self.chips.len());
318 for (chip_id, (chip, n_active)) in self.chips.iter().enumerate() {
319 let call_data = mem::take(&mut pending[chip_id]);
320
321 assert!(*n_active > 0, "chip {chip_id} is never called");
323
324 let log_instances = log2_ceil_usize(call_data.len());
325 let table = chip
326 .circuit
327 .populate_batch_parallel(&GlobalAllocator, log_instances, |instance, filler| {
328 let inout = &call_data[instance.min(call_data.len() - 1)];
330 for (i, &wire) in chip.circuit.inout().iter().enumerate() {
331 filler[wire] = inout.get(i).copied().unwrap_or(Word::ZERO);
332 }
333 })
334 .map_err(|source| PopulateM4Error::Chip { chip_id, source })?;
335
336 if !chip.chip_calls.is_empty() {
339 let constants = &chip.circuit.constraint_system().constants;
340 for instance in 0..*n_active {
341 let values = table.instance_words(instance, constants);
342 for call in &chip.chip_calls {
343 pending[call.chip_id][call.first_instance + instance] =
344 eval_call(&values, call);
345 }
346 }
347 }
348
349 tables.push(table);
350 }
351
352 Ok(WitnessM4 {
353 main: main_values,
354 tables,
355 })
356 }
357}
358
359fn eval_call(values: &impl WordSource, call: &ChipCall) -> Vec<Word> {
361 call.inout
362 .iter()
363 .map(|operand| eval_operand(values, operand))
364 .collect()
365}
366
367#[allow(missing_docs)] #[derive(Debug, thiserror::Error)]
370pub enum PopulateM4Error {
371 #[error("the main circuit is not satisfied: {0}")]
372 Main(#[from] PopulateError),
373 #[error("chip #{chip_id} is not satisfied: {source}")]
374 Chip {
375 chip_id: usize,
376 #[source]
377 source: BatchPopulateError,
378 },
379}
380
381#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
387pub struct ChipRef(usize);
388
389impl ChipRef {
390 pub(crate) const fn new(chip_id: usize) -> Self {
392 Self(chip_id)
393 }
394
395 pub const fn chip_id(self) -> usize {
400 self.0
401 }
402}
403
404pub trait ChipGadget: Hint {
460 fn build(&self, builder: &CircuitBuilder, dimensions: &[usize], inputs: &[Wire]) -> Vec<Wire>;
467}
468
469#[allow(missing_docs)] #[derive(Debug, thiserror::Error)]
472pub enum CircuitM4Error {
473 #[error("{} calls chip {chip_id}, but the system has {n_chips} chips", ChipName(*chip_index))]
474 OutOfRangeChipId {
475 chip_index: Option<usize>,
476 chip_id: usize,
477 n_chips: usize,
478 },
479 #[error(
480 "{}'s call #{call_index} passes {arity} operands to chip {chip_id}, which has {n_inout} inout values",
481 ChipName(*chip_index)
482 )]
483 WrongCallArity {
484 chip_index: Option<usize>,
485 call_index: usize,
486 chip_id: usize,
487 arity: usize,
488 n_inout: usize,
489 },
490 #[error(
491 "{}'s call #{call_index} has a malformed operand #{operand_index}: {source}",
492 ChipName(*chip_index)
493 )]
494 CallOperand {
495 chip_index: Option<usize>,
496 call_index: usize,
497 operand_index: usize,
498 #[source]
499 source: OperandFault,
500 },
501 #[error("chip #{chip_index} calls chip {callee}, which is not a later chip")]
502 CallOutOfOrder { chip_index: usize, callee: usize },
503 #[error(
504 "{}'s call #{call_index} names instance {first_instance}, but the call graph gives it {expected}",
505 ChipName(*chip_index)
506 )]
507 WrongCallInstance {
508 chip_index: Option<usize>,
509 call_index: usize,
510 first_instance: usize,
511 expected: usize,
512 },
513 #[error("chip #{chip_index} declares {declared} active instances, but {actual} calls reach it")]
514 WrongActiveInstanceCount {
515 chip_index: usize,
516 declared: usize,
517 actual: usize,
518 },
519 #[error("chip #{chip_index} is never called")]
520 NeverCalled { chip_index: usize },
521 #[error("more invocations reach chip #{chip_index} than a usize can count")]
522 TooManyInstances { chip_index: usize },
523}
524
525#[cfg(test)]
526mod tests {
527 use std::iter;
528
529 use binius_core::{ShiftedValueIndex, ValueIndex, VerificationM4Error, error::OperandFault};
530
531 use super::*;
532 use crate::{Circuit, CircuitBuilder, CircuitM4Error, EmbeddedCircuit, Wire};
533
534 fn and_chip(callee: usize) -> EmbeddedCircuit {
538 let builder = CircuitBuilder::new();
539 let (a, b, c) = (builder.add_inout(), builder.add_inout(), builder.add_inout());
540 builder.assert_eq("and", builder.band(a, b), c);
541 let circuit = builder.build();
542
543 let forward_c = operand(&circuit, c);
544 EmbeddedCircuit {
545 chip_calls: vec![ChipCall {
546 chip_id: callee,
547 first_instance: 0,
548 inout: vec![forward_c.clone(), forward_c],
549 }],
550 circuit,
551 }
552 }
553
554 fn twice_calling_and_chip(callee: usize) -> EmbeddedCircuit {
558 let builder = CircuitBuilder::new();
559 let (a, b, c) = (builder.add_inout(), builder.add_inout(), builder.add_inout());
560 builder.assert_eq("and", builder.band(a, b), c);
561 let circuit = builder.build();
562
563 let call = |wire| ChipCall {
564 chip_id: callee,
565 first_instance: 0,
566 inout: vec![operand(&circuit, wire), operand(&circuit, wire)],
567 };
568 let chip_calls = vec![call(a), call(b)];
569 EmbeddedCircuit {
570 circuit,
571 chip_calls,
572 }
573 }
574
575 fn promoting_and_chip() -> EmbeddedCircuit {
578 let builder = CircuitBuilder::new();
579 let (a, b) = (builder.add_inout(), builder.add_inout());
580 builder.mark_inout(builder.band(a, b));
581 EmbeddedCircuit {
582 circuit: builder.build(),
583 chip_calls: vec![],
584 }
585 }
586
587 fn eq_chip() -> EmbeddedCircuit {
589 let builder = CircuitBuilder::new();
590 let (x, y) = (builder.add_inout(), builder.add_inout());
591 builder.assert_eq("eq", x, y);
592 EmbeddedCircuit {
593 circuit: builder.build(),
594 chip_calls: vec![],
595 }
596 }
597
598 fn operand(circuit: &Circuit, wire: Wire) -> Vec<ShiftedValueIndex> {
600 vec![ShiftedValueIndex::plain(circuit.witness_index(wire))]
601 }
602
603 fn main_circuit(n_calls: usize) -> (EmbeddedCircuit, Vec<(Wire, Wire)>) {
607 let builder = CircuitBuilder::new();
608 let inputs = (0..n_calls)
609 .map(|_| (builder.add_witness(), builder.add_witness()))
610 .collect::<Vec<_>>();
611 let conjunctions = inputs
612 .iter()
613 .map(|&(a, b)| {
614 let and = builder.band(a, b);
615 builder.mark_inout(and);
616 and
617 })
618 .collect::<Vec<_>>();
619 let circuit = builder.build();
620
621 let chip_calls = iter::zip(&inputs, &conjunctions)
622 .map(|(&(a, b), &and)| ChipCall {
623 chip_id: 0,
624 first_instance: 0,
625 inout: vec![
626 operand(&circuit, a),
627 operand(&circuit, b),
628 operand(&circuit, and),
629 ],
630 })
631 .collect();
632
633 let main = EmbeddedCircuit {
634 circuit,
635 chip_calls,
636 };
637 (main, inputs)
638 }
639
640 fn system(n_calls: usize) -> (CircuitM4, Vec<(Wire, Wire)>) {
643 let (main, inputs) = main_circuit(n_calls);
644 let mut circuit = CircuitM4 {
645 main,
646 chips: vec![(and_chip(1), 0), (eq_chip(), 0)],
647 };
648 circuit.recompute_instances();
649 (circuit, inputs)
650 }
651
652 fn instance_inout(chip: &EmbeddedCircuit, table: &ValueTable, instance: usize) -> Vec<u64> {
654 let constants = &chip.circuit.constraint_system().constants;
655 let values = table.instance_value_vec(instance, constants);
656 chip.circuit
657 .inout()
658 .iter()
659 .map(|&wire| values[chip.circuit.witness_index(wire)].as_u64())
660 .collect()
661 }
662
663 #[test]
666 fn generate_serves_the_calls_a_builder_emitted() {
667 let chip = CircuitBuilder::new();
669 let (x, y, z) = (chip.add_inout(), chip.add_inout(), chip.add_inout());
670 chip.assert_eq("and", chip.band(x, y), z);
671
672 let builder = CircuitBuilder::new();
674 let chip_ref = builder.add_chip(CircuitM4::from(chip.build()));
675 let inputs = (0..2)
676 .map(|_| (builder.add_witness(), builder.add_witness()))
677 .collect::<Vec<_>>();
678 for &(a, b) in &inputs {
679 builder.call_chip(chip_ref, &[a, b, builder.band(a, b)]);
680 }
681 let circuit = builder.build_m4();
682
683 assert_eq!(circuit.chips[0].1, 2);
684 circuit.validate().unwrap();
685
686 let words = [(0b1100u64, 0b1010u64), (0xff00, 0x0ff0)];
687 let witness = circuit
688 .generate_witness(|filler| {
689 for (&(a, b), &(a_word, b_word)) in iter::zip(&inputs, &words) {
690 filler[a] = Word(a_word);
691 filler[b] = Word(b_word);
692 }
693 })
694 .unwrap();
695
696 assert_eq!(witness.tables[0].n_instances(), 2);
697 for (instance, &(a, b)) in words.iter().enumerate() {
698 assert_eq!(
699 instance_inout(&circuit.chips[0].0, &witness.tables[0], instance),
700 vec![a, b, a & b]
701 );
702 }
703 }
704
705 #[test]
706 fn generate_fills_one_instance_per_call() {
707 let (circuit, inputs) = system(2);
708 circuit.validate().unwrap();
709
710 let words = [(0b1100u64, 0b1010u64), (0xff00, 0x0ff0)];
711 let witness = circuit
712 .generate_witness(|filler| {
713 for (&(a, b), &(a_word, b_word)) in iter::zip(&inputs, &words) {
714 filler[a] = Word(a_word);
715 filler[b] = Word(b_word);
716 }
717 })
718 .unwrap();
719
720 let (chip_0, chip_1) = (&circuit.chips[0].0, &circuit.chips[1].0);
722 assert_eq!(witness.tables[0].n_instances(), 2);
723 for (instance, &(a, b)) in words.iter().enumerate() {
724 assert_eq!(instance_inout(chip_0, &witness.tables[0], instance), vec![a, b, a & b]);
725 }
726
727 assert_eq!(witness.tables[1].n_instances(), 2);
729 for (instance, &(a, b)) in words.iter().enumerate() {
730 assert_eq!(instance_inout(chip_1, &witness.tables[1], instance), vec![a & b, a & b]);
731 }
732
733 witness.verify(&circuit.to_constraint_system()).unwrap();
734 }
735
736 #[test]
741 fn generate_recomputes_a_promoted_inout_word() {
742 let (main, inputs) = main_circuit(1);
743 let mut circuit = CircuitM4 {
744 main,
745 chips: vec![(promoting_and_chip(), 1)],
746 };
747 circuit.validate().unwrap();
748
749 let (a, b) = inputs[0];
750 let fill = |filler: &mut WitnessFiller<'_>| {
751 filler[a] = Word(0b1100);
752 filler[b] = Word(0b1010);
753 };
754 let row = |circuit: &CircuitM4, witness: &WitnessM4| {
755 instance_inout(&circuit.chips[0].0, &witness.tables[0], 0)
756 };
757
758 let witness = circuit.generate_witness(fill).unwrap();
759 assert_eq!(row(&circuit, &witness), vec![0b1100, 0b1010, 0b1000]);
760 witness.verify(&circuit.to_constraint_system()).unwrap();
761
762 circuit.main.chip_calls[0].inout[2] = operand(&circuit.main.circuit, a);
766 let witness = circuit.generate_witness(fill).unwrap();
767 assert_eq!(row(&circuit, &witness), vec![0b1100, 0b1010, 0b1000]);
768 let err = witness.verify(&circuit.to_constraint_system()).unwrap_err();
769 assert!(
770 matches!(
771 err,
772 VerificationM4Error::CallMismatch {
773 chip_id: 0,
774 row: 0,
775 caller: None,
776 word: 2,
777 ..
778 }
779 ),
780 "{err}"
781 );
782 }
783
784 #[test]
789 fn generate_gives_each_call_site_a_contiguous_block_of_instances() {
790 let (main, inputs) = main_circuit(2);
791 let mut circuit = CircuitM4 {
792 main,
793 chips: vec![(twice_calling_and_chip(1), 0), (eq_chip(), 0)],
794 };
795 circuit.recompute_instances();
796 circuit.validate().unwrap();
797
798 assert_eq!(circuit.chips[0].1, 2);
800 assert_eq!(circuit.chips[1].1, 4);
801
802 let words = [(0b1100u64, 0b1010u64), (0xff00, 0x0ff0)];
803 let witness = circuit
804 .generate_witness(|filler| {
805 for (&(a, b), &(a_word, b_word)) in iter::zip(&inputs, &words) {
806 filler[a] = Word(a_word);
807 filler[b] = Word(b_word);
808 }
809 })
810 .unwrap();
811
812 let expected = [words[0].0, words[1].0, words[0].1, words[1].1];
815 assert_eq!(witness.tables[1].n_instances(), 4);
816 for (instance, &word) in expected.iter().enumerate() {
817 assert_eq!(
818 instance_inout(&circuit.chips[1].0, &witness.tables[1], instance),
819 vec![word, word]
820 );
821 }
822
823 witness.verify(&circuit.to_constraint_system()).unwrap();
824 }
825
826 #[test]
827 fn generate_pads_the_instance_count_by_repeating_the_last_call() {
828 let (circuit, inputs) = system(3);
829 circuit.validate().unwrap();
830
831 let words = [(0b1100u64, 0b1010u64), (0xff00, 0x0ff0), (0xabcd, 0xdcba)];
832 let witness = circuit
833 .generate_witness(|filler| {
834 for (&(a, b), &(a_word, b_word)) in iter::zip(&inputs, &words) {
835 filler[a] = Word(a_word);
836 filler[b] = Word(b_word);
837 }
838 })
839 .unwrap();
840
841 let chip_0 = &circuit.chips[0].0;
843 assert_eq!(witness.tables[0].n_instances(), 4);
844 let (a, b) = words[2];
845 assert_eq!(instance_inout(chip_0, &witness.tables[0], 3), vec![a, b, a & b]);
846
847 witness.verify(&circuit.to_constraint_system()).unwrap();
850 }
851
852 #[test]
853 fn generate_reports_the_chip_whose_calls_do_not_satisfy_it() {
854 let (mut circuit, inputs) = system(1);
855
856 let (a, b) = inputs[0];
858 circuit.main.chip_calls[0].inout[2] = operand(&circuit.main.circuit, a);
859
860 let err = circuit
861 .generate_witness(|filler| {
862 filler[a] = Word(0b1100);
863 filler[b] = Word(0b1010);
864 })
865 .unwrap_err();
866 assert!(matches!(err, PopulateM4Error::Chip { chip_id: 0, .. }), "{err}");
867 }
868
869 #[test]
870 fn validate_rejects_a_call_to_a_chip_that_does_not_exist() {
871 let (mut circuit, _) = system(1);
872 circuit.main.chip_calls[0].chip_id = 2;
873 assert!(matches!(
874 circuit.validate(),
875 Err(CircuitM4Error::OutOfRangeChipId {
876 chip_index: None,
877 chip_id: 2,
878 n_chips: 2,
879 })
880 ));
881 }
882
883 #[test]
884 fn validate_rejects_chips_out_of_topological_order() {
885 let (mut circuit, _) = system(1);
886 circuit.chips[1].0.chip_calls.push(ChipCall {
888 chip_id: 0,
889 first_instance: 0,
890 inout: vec![],
891 });
892 assert!(matches!(
893 circuit.validate(),
894 Err(CircuitM4Error::CallOutOfOrder {
895 chip_index: 1,
896 callee: 0,
897 })
898 ));
899 }
900
901 #[test]
902 fn validate_rejects_a_wrong_active_instance_count() {
903 let (mut circuit, _) = system(2);
904 circuit.chips[1].1 = 1;
905 assert!(matches!(
906 circuit.validate(),
907 Err(CircuitM4Error::WrongActiveInstanceCount {
908 chip_index: 1,
909 declared: 1,
910 actual: 2,
911 })
912 ));
913 }
914
915 #[test]
919 fn validate_rejects_a_call_naming_the_wrong_instance() {
920 let (mut circuit, _) = system(2);
921 circuit.main.chip_calls[1].first_instance = 0;
922 assert!(matches!(
923 circuit.validate(),
924 Err(CircuitM4Error::WrongCallInstance {
925 chip_index: None,
926 call_index: 1,
927 first_instance: 0,
928 expected: 1,
929 })
930 ));
931 }
932
933 #[test]
934 fn validate_rejects_a_chip_nothing_calls() {
935 let (mut circuit, _) = system(1);
936 circuit.main.chip_calls.clear();
937 circuit.recompute_instances();
938 assert!(matches!(circuit.validate(), Err(CircuitM4Error::NeverCalled { chip_index: 0 })));
939 }
940
941 #[test]
944 fn validate_rejects_a_call_passing_more_operands_than_the_callee_takes() {
945 let (mut circuit, inputs) = system(1);
946 let extra = operand(&circuit.main.circuit, inputs[0].0);
947 circuit.main.chip_calls[0].inout.push(extra);
948 assert!(matches!(
949 circuit.validate(),
950 Err(CircuitM4Error::WrongCallArity {
951 chip_index: None,
952 call_index: 0,
953 chip_id: 0,
954 arity: 4,
955 n_inout: 3,
956 })
957 ));
958 }
959
960 #[test]
963 fn validate_rejects_a_call_operand_naming_a_scratch_value() {
964 let (mut circuit, _) = system(1);
965 circuit.main.chip_calls[0].inout[2] =
966 vec![ShiftedValueIndex::plain(ValueIndex::scratch(0))];
967 assert!(matches!(
968 circuit.validate(),
969 Err(CircuitM4Error::CallOperand {
970 chip_index: None,
971 call_index: 0,
972 operand_index: 2,
973 source: OperandFault::ScratchValueIndex,
974 })
975 ));
976 }
977
978 #[test]
981 fn validate_rejects_an_instance_count_that_outgrows_a_usize() {
982 let chip = |callee: Option<usize>| {
984 let builder = CircuitBuilder::new();
985 builder.add_inout();
986 EmbeddedCircuit {
987 circuit: builder.build(),
988 chip_calls: callee
989 .into_iter()
990 .flat_map(|chip_id| {
991 iter::repeat_with(move || ChipCall {
992 chip_id,
993 first_instance: 0,
994 inout: vec![],
995 })
996 .take(2)
997 })
998 .collect(),
999 }
1000 };
1001
1002 const DEPTH: usize = 70;
1003 let mut circuit = CircuitM4 {
1004 main: EmbeddedCircuit {
1005 circuit: CircuitBuilder::new().build(),
1006 chip_calls: vec![ChipCall {
1007 chip_id: 0,
1008 first_instance: 0,
1009 inout: vec![],
1010 }],
1011 },
1012 chips: (0..DEPTH)
1013 .map(|i| (chip((i + 1 < DEPTH).then_some(i + 1)), 0))
1014 .collect(),
1015 };
1016 circuit.recompute_instances();
1017
1018 assert!(matches!(
1021 circuit.validate(),
1022 Err(CircuitM4Error::TooManyInstances { chip_index: 64 })
1023 ));
1024 }
1025}