binius_m3/gadgets/
add.rs

1// Copyright 2025 Irreducible Inc.
2
3use std::{array, marker::PhantomData, ops::Deref};
4
5use binius_core::oracle::ShiftVariant;
6use binius_field::{
7	Field, PackedExtension, PackedField, PackedFieldIndexable, PackedSubfield, TowerField,
8	packed::set_packed_slice,
9};
10use itertools::izip;
11
12use crate::builder::{
13	B32, B64, B128, TableBuilder, column::Col, types::B1, witness::TableWitnessSegment,
14};
15
16/// A gadget for performing 32-bit integer addition on vertically-packed bit columns.
17///
18/// This gadget has input columns `xin` and `yin` for the two 32-bit integers to be added, and an
19/// output column `zout`, and it constrains that `xin + yin = zout` as integers.
20#[derive(Debug)]
21pub struct U32Add {
22	// Inputs
23	pub xin: Col<B1, 32>,
24	pub yin: Col<B1, 32>,
25
26	// Private
27	cin: Col<B1, 32>,
28	cout: Col<B1, 32>,
29	cout_shl: Col<B1, 32>,
30
31	// Outputs
32	/// The output column, either committed if `flags.commit_zout` is set, otherwise a linear
33	/// combination derived column.
34	pub zout: Col<B1, 32>,
35	/// This is `Some` if `flags.expose_final_carry` is set, otherwise it is `None`.
36	pub final_carry: Option<Col<B1>>,
37	/// Flags modifying the gadget's behavior.
38	pub flags: U32AddFlags,
39}
40
41/// Flags modifying the behavior of the [`U32Add`] gadget.
42#[derive(Debug, Default, Clone)]
43pub struct U32AddFlags {
44	// Optionally a column for a dynamic carry in bit. This *must* be zero in all bits except the
45	// 0th.
46	pub carry_in_bit: Option<Col<B1, 32>>,
47	pub commit_zout: bool,
48	pub expose_final_carry: bool,
49}
50
51impl U32Add {
52	pub fn new(
53		table: &mut TableBuilder,
54		xin: Col<B1, 32>,
55		yin: Col<B1, 32>,
56		flags: U32AddFlags,
57	) -> Self {
58		let cout = table.add_committed::<B1, 32>("cout");
59		let cout_shl = table.add_shifted("cout_shl", cout, 5, 1, ShiftVariant::LogicalLeft);
60
61		let cin = if let Some(carry_in_bit) = flags.carry_in_bit {
62			table.add_computed("cin", cout_shl + carry_in_bit)
63		} else {
64			cout_shl
65		};
66
67		let final_carry = flags
68			.expose_final_carry
69			.then(|| table.add_selected("final_carry", cout, 31));
70
71		table.assert_zero("carry_out", (xin + cin) * (yin + cin) + cin - cout);
72
73		let zout = if flags.commit_zout {
74			let zout = table.add_committed::<B1, 32>("zout");
75			table.assert_zero("zout", xin + yin + cin - zout);
76			zout
77		} else {
78			table.add_computed("zout", xin + yin + cin)
79		};
80
81		Self {
82			xin,
83			yin,
84			cin,
85			cout,
86			cout_shl,
87			final_carry,
88			zout,
89			flags,
90		}
91	}
92
93	pub fn populate<P>(&self, index: &mut TableWitnessSegment<P>) -> Result<(), anyhow::Error>
94	where
95		P: PackedFieldIndexable<Scalar = B128> + PackedExtension<B1>,
96	{
97		let xin: std::cell::RefMut<'_, [u32]> = index.get_mut_as(self.xin)?;
98		let yin = index.get_mut_as(self.yin)?;
99		let mut cout = index.get_mut_as(self.cout)?;
100		let mut zout = index.get_mut_as(self.zout)?;
101		let mut final_carry = if let Some(final_carry) = self.final_carry {
102			let final_carry = index.get_mut(final_carry)?;
103			Some(final_carry)
104		} else {
105			None
106		};
107
108		if let Some(carry_in_bit_col) = self.flags.carry_in_bit {
109			// This is u32 assumed to be either 0 or 1.
110			let carry_in_bit = index.get_mut_as(carry_in_bit_col)?;
111
112			let mut cin = index.get_mut_as(self.cin)?;
113			let mut cout_shl = index.get_mut_as(self.cout_shl)?;
114			for i in 0..index.size() {
115				let (x_plus_y, carry0) = xin[i].overflowing_add(yin[i]);
116				let carry1;
117				(zout[i], carry1) = x_plus_y.overflowing_add(carry_in_bit[i]);
118				let carry = carry0 | carry1;
119
120				cin[i] = xin[i] ^ yin[i] ^ zout[i];
121				cout[i] = (carry as u32) << 31 | cin[i] >> 1;
122				cout_shl[i] = cout[i] << 1;
123
124				if let Some(ref mut final_carry) = final_carry {
125					set_packed_slice(&mut *final_carry, i, if carry { B1::ONE } else { B1::ZERO });
126				}
127			}
128		} else {
129			// When the carry in bit is fixed to zero, we can simplify the logic.
130			let mut cin = index.get_mut_as(self.cin)?;
131			for i in 0..index.size() {
132				let carry;
133				(zout[i], carry) = xin[i].overflowing_add(yin[i]);
134				cin[i] = xin[i] ^ yin[i] ^ zout[i];
135				cout[i] = (carry as u32) << 31 | cin[i] >> 1;
136				if let Some(ref mut final_carry) = final_carry {
137					set_packed_slice(&mut *final_carry, i, if carry { B1::ONE } else { B1::ZERO });
138				}
139			}
140		};
141		Ok(())
142	}
143}
144
145/// A gadget for performing SIMD 32-bit integer addition on vertically-packed bit columns.
146///
147/// This gadget has input columns `xin` and `yin` for the two 32-bit adjacent integers to be added,
148/// and an output column `zout`, and it constrains that `xin + yin = zout` as integers. Only
149/// A gadget for performing SIMD 32-bit integer addition on vertically-packed bit columns.
150///
151/// This gadget has input columns `xin` and `yin` for the two 32-bit adjacent integers to be added,
152/// and an output column `zout`, and it constrains that `xin + yin = zout` as integers. Only
153/// supports unchecked addition, so it does not handle overflow.
154///
155/// The generic parameter `V` represents the number of vertically-packed bits (the vertical stacking
156/// factor of the bit columns). The default value is 32, which corresponds to the standard 32-bit
157/// integer size. When using a different value for `V`, ensure it is a power of two and at least 32.
158#[derive(Debug)]
159pub struct U32AddStacked<const V: usize = 32> {
160	// Inputs
161	pub xin: Col<B1, V>,
162	pub yin: Col<B1, V>,
163
164	// Private
165	cin: Col<B1, V>,
166	cout: Col<B1, V>,
167	cout_shl: Col<B1, V>,
168
169	carry_in_bit: Option<Col<B1, V>>,
170	pub zout: Col<B1, V>,
171}
172
173impl<const V: usize> U32AddStacked<V> {
174	pub fn new(
175		table: &mut TableBuilder,
176		xin: Col<B1, V>,
177		yin: Col<B1, V>,
178		commit_zout: bool,
179		carry_in_bit: Option<Col<B1, V>>,
180	) -> Self {
181		assert!(
182			V.is_power_of_two() && V >= 32,
183			"V must be a power of two greater than or equal to 32"
184		);
185		let cout = table.add_committed::<B1, V>("cout");
186		let cout_shl = table.add_shifted("cout_shl", cout, 5, 1, ShiftVariant::LogicalLeft);
187
188		let cin = if let Some(carry_in_bit) = carry_in_bit {
189			table.add_computed("cin", cout_shl + carry_in_bit)
190		} else {
191			cout_shl
192		};
193
194		table.assert_zero("carry_out", (xin + cin) * (yin + cin) + cin - cout);
195
196		let zout = if commit_zout {
197			let zout = table.add_committed::<B1, V>("zout");
198			table.assert_zero("zout", xin + yin + cin - zout);
199			zout
200		} else {
201			table.add_computed("zout", xin + yin + cin)
202		};
203
204		Self {
205			xin,
206			yin,
207			cin,
208			cout,
209			cout_shl,
210			zout,
211			carry_in_bit,
212		}
213	}
214
215	pub fn populate<P>(&self, index: &mut TableWitnessSegment<P>) -> Result<(), anyhow::Error>
216	where
217		P: PackedFieldIndexable<Scalar = B128> + PackedExtension<B1>,
218	{
219		let stacking_factor = V / 32;
220		let xin: std::cell::RefMut<'_, [u32]> = index.get_mut_as(self.xin)?;
221		let yin = index.get_mut_as(self.yin)?;
222		let mut cout = index.get_mut_as(self.cout)?;
223		let mut zout = index.get_mut_as(self.zout)?;
224
225		if let Some(carry_in_bit_col) = self.carry_in_bit {
226			// This is u32 assumed to be either 0 or 1.
227			let carry_in_bit = index.get_mut_as(carry_in_bit_col)?;
228
229			let mut cin = index.get_mut_as(self.cin)?;
230			let mut cout_shl = index.get_mut_as(self.cout_shl)?;
231
232			for i in 0..index.size() * stacking_factor {
233				let (x_plus_y, carry0) = xin[i].overflowing_add(yin[i]);
234				let carry1;
235				(zout[i], carry1) = x_plus_y.overflowing_add(carry_in_bit[i]);
236				let carry = carry0 | carry1;
237
238				cin[i] = xin[i] ^ yin[i] ^ zout[i];
239				cout[i] = (carry as u32) << 31 | cin[i] >> 1;
240				cout_shl[i] = cout[i] << 1;
241			}
242		} else {
243			// When the carry in bit is fixed to zero, we can simplify the logic.
244			let mut cin = index.get_mut_as(self.cin)?;
245			for i in 0..index.size() * stacking_factor {
246				let carry;
247				(zout[i], carry) = xin[i].overflowing_add(yin[i]);
248				cin[i] = xin[i] ^ yin[i] ^ zout[i];
249				cout[i] = (carry as u32) << 31 | cin[i] >> 1;
250			}
251		};
252		Ok(())
253	}
254}
255/// Gadget for unsigned addition using non-packed one-bit columns generic over `u32` and `u64`
256#[derive(Debug)]
257pub struct WideAdd<UX: UnsignedAddPrimitives, const BIT_LENGTH: usize> {
258	pub x_in: [Col<B1>; BIT_LENGTH],
259	pub y_in: [Col<B1>; BIT_LENGTH],
260
261	cin: [Col<B1>; BIT_LENGTH],
262
263	pub z_out: [Col<B1>; BIT_LENGTH],
264	pub final_carry_out: Col<B1>,
265	/// This gadget always exposes the final carry bit.
266	pub flags: U32AddFlags,
267	// TODO: Maybe make a serperate flag for handling unsigned adds of arbitrary bit length.
268	_marker: PhantomData<UX>,
269}
270impl<UX: UnsignedAddPrimitives, const BIT_LENGTH: usize> WideAdd<UX, BIT_LENGTH> {
271	pub fn new(
272		table: &mut TableBuilder,
273		x_in: [Col<B1>; BIT_LENGTH],
274		y_in: [Col<B1>; BIT_LENGTH],
275		flags: U32AddFlags,
276	) -> Self {
277		assert_eq!(BIT_LENGTH, UX::BIT_LENGTH);
278		let cin = array::from_fn(|i| {
279			if i != 0 {
280				table.add_committed(format!("cin[{i}]"))
281			} else {
282				flags
283					.carry_in_bit
284					.map(|carry_col| table.add_selected("cin[0]", carry_col, 0))
285					.unwrap_or_else(|| table.add_committed("cin[0]"))
286			}
287		});
288		let zout = if flags.commit_zout {
289			let zout = table.add_committed_multiple("z_out");
290			for bit in 0..BIT_LENGTH {
291				table.assert_zero(
292					format!("sum[{bit}]"),
293					x_in[bit] + y_in[bit] + cin[bit] - zout[bit],
294				);
295			}
296			zout
297		} else {
298			array::from_fn(|i| table.add_computed(format!("zout[{i}]"), x_in[i] + y_in[i] + cin[i]))
299		};
300		let final_carry_out = table.add_committed("final_carry_out");
301		let cout: [_; BIT_LENGTH] = array::from_fn(|i| {
302			if i != BIT_LENGTH - 1 {
303				cin[i + 1]
304			} else {
305				final_carry_out
306			}
307		});
308		for bit in 0..BIT_LENGTH {
309			table.assert_zero(
310				format!("carry_{bit}"),
311				(x_in[bit] + cin[bit]) * (y_in[bit] + cin[bit]) + cin[bit] - cout[bit],
312			);
313		}
314
315		Self {
316			x_in,
317			y_in,
318			cin,
319			z_out: zout,
320			final_carry_out,
321			flags,
322			_marker: PhantomData,
323		}
324	}
325
326	pub fn populate<P>(&self, index: &mut TableWitnessSegment<P>) -> Result<(), anyhow::Error>
327	where
328		P: PackedField<Scalar = B128> + PackedExtension<B1>,
329	{
330		let x_in = array_util::try_map(self.x_in, |bit| index.get(bit))?;
331		let y_in = array_util::try_map(self.y_in, |bit| index.get(bit))?;
332		let mut cin = array_util::try_map(self.cin, |bit| index.get_mut(bit))?;
333		let mut zout = array_util::try_map(self.z_out, |bit| index.get_mut(bit))?;
334		let mut final_carry_out = index.get_mut(self.final_carry_out)?;
335
336		let mut carry_in = if self.flags.carry_in_bit.is_none() {
337			let initial_carry = PackedSubfield::<P, B1>::default();
338			vec![initial_carry; cin[0].len()]
339		} else {
340			index
341				.get(self.flags.carry_in_bit.expect("carry_in_bit is not None"))?
342				.deref()
343				.to_vec()
344		};
345		for bit in 0..BIT_LENGTH {
346			for (x_in_packed, y_in_packed, c_in_packed, zout_packed, carry_in) in izip!(
347				x_in[bit].iter(),
348				y_in[bit].iter(),
349				cin[bit].iter_mut(),
350				zout[bit].iter_mut(),
351				carry_in.iter_mut()
352			) {
353				let sum = *x_in_packed + *y_in_packed + *carry_in;
354				let new_carry = (*x_in_packed + *carry_in) * (*y_in_packed + *carry_in) + *carry_in;
355				*c_in_packed = *carry_in;
356				*zout_packed = sum;
357				*carry_in = new_carry;
358			}
359		}
360
361		for (final_carry, carry) in izip!(final_carry_out.iter_mut(), carry_in.iter()) {
362			*final_carry = *carry;
363		}
364
365		Ok(())
366	}
367}
368
369/// A very simple trait used in Addition gadgets for unsigned integers of different bit lengths.
370pub trait UnsignedAddPrimitives {
371	type F: TowerField;
372	const BIT_LENGTH: usize;
373}
374
375impl UnsignedAddPrimitives for u32 {
376	type F = B32;
377
378	const BIT_LENGTH: usize = 32;
379}
380
381impl UnsignedAddPrimitives for u64 {
382	type F = B64;
383
384	const BIT_LENGTH: usize = 64;
385}
386
387/// Gadget for incrementing the values in a column by 1, generic over `u32` and `u64`
388#[derive(Debug)]
389pub struct Incr<UPrimitive: UnsignedAddPrimitives, const BIT_LENGTH: usize> {
390	cin: [Col<B1>; BIT_LENGTH],
391
392	/// Input column as bits.
393	pub input: [Col<B1>; BIT_LENGTH],
394	/// Output column as bits.
395	pub zout: [Col<B1>; BIT_LENGTH],
396	/// Output carry bit if there is any.
397	pub final_carry_out: Col<B1>,
398
399	_marker: PhantomData<UPrimitive>,
400}
401
402impl<UPrimitive: UnsignedAddPrimitives, const BIT_LENGTH: usize> Incr<UPrimitive, BIT_LENGTH> {
403	pub fn new(table: &mut TableBuilder, input: [Col<B1>; BIT_LENGTH]) -> Self {
404		assert_eq!(BIT_LENGTH, UPrimitive::BIT_LENGTH);
405		let cin = array::from_fn(|i| {
406			if i != 0 {
407				table.add_committed(format!("cout[{i}]"))
408			} else {
409				table.add_constant("cout[0]", [B1::ONE])
410			}
411		});
412		let zout = table.add_committed_multiple("zout");
413		let final_carry_out = table.add_committed("final_carry_out");
414		let cout: [_; BIT_LENGTH] = array::from_fn(|i| {
415			if i != BIT_LENGTH - 1 {
416				cin[i + 1]
417			} else {
418				final_carry_out
419			}
420		});
421
422		for (i, &xin) in input.iter().enumerate() {
423			table.assert_zero(format!("sum[{i}]"), xin + cin[i] - zout[i]);
424			table.assert_zero(format!("carry[{i}]"), xin * cin[i] - cout[i]);
425		}
426
427		Self {
428			cin,
429			input,
430			zout,
431			final_carry_out,
432			_marker: PhantomData,
433		}
434	}
435
436	pub fn populate<P>(&self, index: &mut TableWitnessSegment<P>) -> Result<(), anyhow::Error>
437	where
438		P: PackedField<Scalar = B128> + PackedExtension<B1>,
439	{
440		// Get the input bits (read-only)
441		let in_bits = array_util::try_map(self.input, |bit| index.get(bit))?;
442		// Get the carry-in bits
443		let mut cin = array_util::try_map(self.cin, |bit| index.get_mut(bit))?;
444		// Get the output bits
445		let mut zout = array_util::try_map(self.zout, |bit| index.get_mut(bit))?;
446		let mut final_carry_out = index.get_mut(self.final_carry_out)?;
447
448		let p_inv = PackedSubfield::<P, B1>::broadcast(B1::ONE);
449		let mut carry_in = vec![p_inv; cin[0].len()];
450		for bit in 0..BIT_LENGTH {
451			for (in_bit, cin, zout, carry_in) in izip!(
452				in_bits[bit].iter(),
453				cin[bit].iter_mut(),
454				zout[bit].iter_mut(),
455				carry_in.iter_mut()
456			) {
457				let sum = *in_bit + *carry_in;
458				let new_carry = (*in_bit) * (*carry_in);
459				*cin = *carry_in;
460				*zout = sum;
461				*carry_in = new_carry;
462			}
463		}
464		for (final_carry, carry) in izip!(final_carry_out.iter_mut(), carry_in.iter()) {
465			*final_carry = *carry;
466		}
467
468		Ok(())
469	}
470}
471
472pub type U32Incr = Incr<u32, 32>;
473pub type U64Incr = Incr<u64, 64>;
474
475#[cfg(test)]
476mod tests {
477	use std::iter::repeat_with;
478
479	use binius_compute::cpu::alloc::CpuComputeAllocator;
480	use binius_field::{
481		arch::OptimalUnderlier128b, as_packed_field::PackedType, packed::get_packed_slice,
482	};
483	use rand::{Rng as _, SeedableRng, prelude::StdRng};
484
485	use super::*;
486	use crate::builder::{ConstraintSystem, WitnessIndex, test_utils::validate_system_witness};
487
488	#[test]
489	fn prop_test_no_carry() {
490		const N_ITER: usize = 1 << 14;
491
492		let mut rng = StdRng::seed_from_u64(0);
493		let test_vector: Vec<(u32, u32, u32, u32, bool)> = (0..N_ITER)
494			.map(|_| {
495				let x: u32 = rng.random();
496				let y: u32 = rng.random();
497				let (z, carry) = x.overflowing_add(y);
498				// (x, y, carry_in, zout, final_carry)
499				(x, y, 0x00000000, z, carry)
500			})
501			.collect();
502
503		TestPlan {
504			dyn_carry_in: false,
505			expose_final_carry: true,
506			commit_zout: false,
507			test_vector,
508		}
509		.execute();
510	}
511
512	#[test]
513	fn prop_test_with_carry() {
514		const N_ITER: usize = 1 << 14;
515
516		let mut rng = StdRng::seed_from_u64(0);
517		let test_vector: Vec<(u32, u32, u32, u32, bool)> = (0..N_ITER)
518			.map(|_| {
519				let x: u32 = rng.random();
520				let y: u32 = rng.random();
521				let carry_in = rng.random::<bool>() as u32;
522				let (x_plus_y, carry1) = x.overflowing_add(y);
523				let (z, carry2) = x_plus_y.overflowing_add(carry_in);
524				let final_carry = carry1 | carry2;
525				(x, y, carry_in, z, final_carry)
526			})
527			.collect();
528
529		TestPlan {
530			dyn_carry_in: true,
531			expose_final_carry: true,
532			commit_zout: true,
533			test_vector,
534		}
535		.execute();
536	}
537
538	#[test]
539	fn test_add_with_carry() {
540		// (x, y, carry_in, zout, final_carry)
541		let test_vector = [
542			(0xFFFFFFFF, 0x00000001, 0x00000000, 0x00000000, true), // max + 1 = 0 (overflow)
543			(0xFFFFFFFF, 0x00000000, 0x00000000, 0xFFFFFFFF, false), // max + 0 = max (no overflow)
544			(0x7FFFFFFF, 0x00000001, 0x00000000, 0x80000000, false), // Sign bit transition
545			(0xFFFF0000, 0x0000FFFF, 0x00000001, 0x00000000, true), // overflow with carry_in
546		];
547		TestPlan {
548			dyn_carry_in: true,
549			expose_final_carry: true,
550			commit_zout: false,
551			test_vector: test_vector.to_vec(),
552		}
553		.execute();
554	}
555
556	struct TestPlan {
557		dyn_carry_in: bool,
558		expose_final_carry: bool,
559		commit_zout: bool,
560		/// (x, y, carry_in, zout, final_carry)
561		test_vector: Vec<(u32, u32, u32, u32, bool)>,
562	}
563
564	impl TestPlan {
565		fn execute(self) {
566			let mut cs = ConstraintSystem::new();
567			let mut table = cs.add_table("u32_add");
568
569			let xin = table.add_committed::<B1, 32>("xin");
570			let yin = table.add_committed::<B1, 32>("yin");
571
572			let carry_in = self
573				.dyn_carry_in
574				.then_some(table.add_committed::<B1, 32>("carry_in"));
575
576			let flags = U32AddFlags {
577				carry_in_bit: carry_in,
578				expose_final_carry: self.expose_final_carry,
579				commit_zout: self.commit_zout,
580			};
581			let adder = U32Add::new(&mut table, xin, yin, flags);
582			assert!(adder.final_carry.is_some() == self.expose_final_carry);
583
584			let table_id = table.id();
585			let mut allocator = CpuComputeAllocator::new(1 << 16);
586			let allocator = allocator.into_bump_allocator();
587			let mut witness =
588				WitnessIndex::<PackedType<OptimalUnderlier128b, B128>>::new(&cs, &allocator);
589
590			let table_witness = witness
591				.init_table(table_id, self.test_vector.len())
592				.unwrap();
593			let mut segment = table_witness.full_segment();
594
595			{
596				let mut xin_bits = segment.get_mut_as::<u32, _, 32>(adder.xin).unwrap();
597				let mut yin_bits = segment.get_mut_as::<u32, _, 32>(adder.yin).unwrap();
598				let mut carry_in_bits =
599					carry_in.map(|carry_in| segment.get_mut_as::<u32, _, 32>(carry_in).unwrap());
600				for (i, (x, y, carry_in, _, _)) in self.test_vector.iter().enumerate() {
601					xin_bits[i] = *x;
602					yin_bits[i] = *y;
603					if let Some(ref mut carry_in_bits) = carry_in_bits {
604						carry_in_bits[i] = *carry_in;
605					}
606				}
607			}
608
609			// Populate the gadget
610			adder.populate(&mut segment).unwrap();
611
612			{
613				// Verify results
614				let zout_bits = segment.get_as::<u32, _, 32>(adder.zout).unwrap();
615				let final_carry = adder
616					.final_carry
617					.map(|final_carry| segment.get(final_carry).unwrap());
618				for (i, (_, _, _, zout, expected_carry)) in self.test_vector.iter().enumerate() {
619					assert_eq!(zout_bits[i], *zout);
620
621					if let Some(ref final_carry) = final_carry {
622						assert_eq!(get_packed_slice(final_carry, i), B1::from(*expected_carry));
623					}
624				}
625			}
626
627			// Validate constraint system
628			let ccs = cs.compile().unwrap();
629			let table_sizes = witness.table_sizes();
630			let witness = witness.into_multilinear_extension_index();
631
632			binius_core::constraint_system::validate::validate_witness(
633				&ccs,
634				&[],
635				&table_sizes,
636				&witness,
637			)
638			.unwrap();
639		}
640	}
641
642	#[test]
643	fn test_incr() {
644		const TABLE_SIZE: usize = 1 << 9;
645
646		let mut cs = ConstraintSystem::new();
647		let mut table = cs.add_table("u32_incr");
648
649		let table_id = table.id();
650		let mut rng = StdRng::seed_from_u64(0);
651		let test_values = repeat_with(|| B32::new(rng.random::<u32>()))
652			.take(TABLE_SIZE)
653			.collect::<Vec<_>>();
654
655		let xin = table.add_committed_multiple("xin");
656
657		let incr = U32Incr::new(&mut table, xin);
658
659		let mut allocator = CpuComputeAllocator::new(1 << 12);
660		let allocator = allocator.into_bump_allocator();
661		let mut witness =
662			WitnessIndex::<PackedType<OptimalUnderlier128b, B128>>::new(&cs, &allocator);
663
664		let table_witness = witness.init_table(table_id, TABLE_SIZE).unwrap();
665		let mut segment = table_witness.full_segment();
666
667		{
668			let mut xin_witness =
669				array_util::try_map(xin, |bit_col| segment.get_mut(bit_col)).unwrap();
670			for (i, value) in test_values.iter().enumerate() {
671				for (bit, packed) in xin_witness.iter_mut().enumerate() {
672					set_packed_slice(packed, i, B1::from(((value.val() >> bit) & 1) == 1))
673				}
674			}
675		}
676
677		incr.populate(&mut segment).unwrap();
678
679		{
680			let zouts = array_util::try_map(incr.zout, |bit_col| segment.get(bit_col)).unwrap();
681			for (i, value) in test_values.iter().enumerate() {
682				let expected = value.val().wrapping_add(1);
683				let mut got = 0u32;
684				for bit in (0..32).rev() {
685					got <<= 1;
686					if get_packed_slice(&zouts[bit], i) == B1::ONE {
687						got |= 1;
688					}
689				}
690				assert_eq!(expected, got);
691			}
692		}
693		// Validate constraint system
694		validate_system_witness::<OptimalUnderlier128b>(&cs, witness, vec![]);
695	}
696
697	#[test]
698	fn test_u32_add_stacked() {
699		let mut cs = ConstraintSystem::new();
700		let mut table = cs.add_table("u32_add_stacked");
701		const TABLE_SIZE: usize = 1 << 6;
702		const LOG_STACKING_FACTOR: usize = 2;
703		const TOTAL_BITS: usize = 32 << LOG_STACKING_FACTOR;
704		let xin = table.add_committed::<B1, TOTAL_BITS>("xin");
705		let yin = table.add_committed::<B1, TOTAL_BITS>("yin");
706		let add = U32AddStacked::new(&mut table, xin, yin, true, None);
707		let table_id = table.id();
708
709		let mut rng = StdRng::seed_from_u64(0);
710		let test_values: Vec<(u32, u32)> = (0..TABLE_SIZE << LOG_STACKING_FACTOR)
711			.map(|_| (rng.random::<u32>(), rng.random::<u32>()))
712			.collect();
713
714		let mut allocator = CpuComputeAllocator::new(1 << 12);
715		let allocator = allocator.into_bump_allocator();
716		let mut witness =
717			WitnessIndex::<PackedType<OptimalUnderlier128b, B128>>::new(&cs, &allocator);
718		let table_witness = witness.init_table(table_id, TABLE_SIZE).unwrap();
719		let mut segment = table_witness.full_segment();
720
721		{
722			let mut xin_bits = segment.get_mut_as::<u32, _, TOTAL_BITS>(add.xin).unwrap();
723			let mut yin_bits = segment.get_mut_as::<u32, _, TOTAL_BITS>(add.yin).unwrap();
724			for (i, (x, y)) in test_values.iter().enumerate() {
725				xin_bits[i] = *x;
726				yin_bits[i] = *y;
727			}
728		}
729
730		add.populate(&mut segment).unwrap();
731
732		{
733			let zout_bits = segment.get_mut_as::<u32, _, TOTAL_BITS>(add.zout).unwrap();
734
735			for (i, (x, y)) in test_values.iter().enumerate() {
736				let expected = x.wrapping_add(*y);
737				assert_eq!(zout_bits[i], expected, "row {i}: {} + {} != {}", x, y, zout_bits[i]);
738			}
739		}
740
741		validate_system_witness::<OptimalUnderlier128b>(&cs, witness, vec![]);
742	}
743}