1use binius_frontend::{CircuitBuilder, Wire};
34
35use crate::util::clear_high_bits;
36
37pub fn popcount(builder: &mut CircuitBuilder, input: Wire) -> Wire {
49 let mask_5555 = builder.add_constant_64(0x5555555555555555); let mask_3333 = builder.add_constant_64(0x3333333333333333); let mask_0f0f = builder.add_constant_64(0x0F0F0F0F0F0F0F0F); let mask_00ff = builder.add_constant_64(0x00FF00FF00FF00FF); let mask_0000ffff = builder.add_constant_64(0x0000FFFF0000FFFF); let n_shr_1 = builder.shr(input, 1);
62 let masked_shr_1 = builder.band(n_shr_1, mask_5555);
63 let zero = builder.add_constant_64(0);
64 let (n_step1, _borrow) = builder.isub_bin_bout(input, masked_shr_1, zero);
65
66 let n_masked_3333 = builder.band(n_step1, mask_3333);
74 let n_shr_2 = builder.shr(n_step1, 2);
75 let n_shr_2_masked = builder.band(n_shr_2, mask_3333);
76 let (n_step2, _carry) = builder.iadd(n_masked_3333, n_shr_2_masked);
77
78 let n_shr_4 = builder.shr(n_step2, 4);
84 let (n_sum3, _carry) = builder.iadd(n_step2, n_shr_4);
85 let n_step3 = builder.band(n_sum3, mask_0f0f);
86
87 let n_shr_8 = builder.shr(n_step3, 8);
90 let (n_sum4, _carry) = builder.iadd(n_step3, n_shr_8);
91 let n_step4 = builder.band(n_sum4, mask_00ff);
92
93 let n_shr_16 = builder.shr(n_step4, 16);
96 let (n_sum5, _carry) = builder.iadd(n_step4, n_shr_16);
97 let n_step5 = builder.band(n_sum5, mask_0000ffff);
98
99 let n_shr_32 = builder.shr(n_step5, 32);
102 let (n_sum6, _carry) = builder.iadd(n_step5, n_shr_32);
103
104 clear_high_bits(builder, n_sum6, 32)
106}
107
108#[cfg(test)]
109mod tests {
110 use binius_core::word::Word;
114 use proptest::prelude::*;
115
116 use super::*;
117
118 fn build_popcount_circuit() -> (binius_frontend::Circuit, Wire, Wire) {
120 let mut builder = CircuitBuilder::new();
121 let input = builder.add_witness();
122 let output = builder.add_witness();
123 let computed = popcount(&mut builder, input);
124 builder.assert_eq("popcount_result", computed, output);
125 let circuit = builder.build();
126 (circuit, input, output)
127 }
128
129 fn test_popcount_value(value: u64) {
131 let (circuit, input, output) = build_popcount_circuit();
132 let mut w = circuit.new_witness_filler();
133
134 let expected = value.count_ones() as u64;
135 w[input] = Word(value);
136 w[output] = Word(expected);
137
138 circuit
139 .populate_wire_witness(&mut w)
140 .unwrap_or_else(|_| panic!("Popcount of 0x{:016x} should be {}", value, expected));
141 }
142
143 #[test]
144 fn test_popcount_basic() {
145 let (circuit, input, output) = build_popcount_circuit();
147
148 let mut w = circuit.new_witness_filler();
150
151 let test_value = 0b10110101u64; let expected_count = test_value.count_ones() as u64;
154 assert_eq!(expected_count, 5, "Expected count should be 5");
155
156 w[input] = Word(test_value);
157 w[output] = Word(expected_count);
158
159 circuit
161 .populate_wire_witness(&mut w)
162 .expect("Circuit should be satisfied with correct popcount");
163 }
164
165 proptest! {
166 #[test]
167 fn test_popcount_edge_cases(
168 value in prop::sample::select(vec![
169 0x0000000000000000u64, 0xFFFFFFFFFFFFFFFFu64, 0x5555555555555555u64, 0xAAAAAAAAAAAAAAAAu64, 0x00000000000000FFu64, 0x000000000000FFFFu64, 0x00000000FFFFFFFFu64, 0x0F0F0F0F0F0F0F0Fu64, 0xCCCCCCCCCCCCCCCCu64, ])
179 ) {
180 test_popcount_value(value);
181 }
182
183 #[test]
184 fn test_single_bit_positions(bit_pos in 0u32..64) {
185 test_popcount_value(1u64 << bit_pos);
187 }
188
189 #[test]
190 fn test_all_bits_except_one(byte_idx in 0usize..8) {
191 test_popcount_value(0xFFFFFFFFFFFFFFFF ^ (1u64 << (byte_idx * 8)));
193 }
194 }
195
196 proptest! {
197 #[test]
198 fn test_popcount_known_bit_counts(
199 value in prop::sample::select(vec![
200 0x0000000000000001u64, 0x0000000000000003u64, 0x000000000000000Fu64, 0x00000000000000FFu64, 0x000000000000FFFFu64, 0x00000000FFFFFFFFu64, 0x7FFFFFFFFFFFFFFFu64, ])
208 ) {
209 test_popcount_value(value);
210 }
211 }
212
213 proptest! {
214 #![proptest_config(ProptestConfig::with_cases(100))]
215 #[test]
216 fn test_popcount_random_values(value in any::<u64>()) {
217 test_popcount_value(value);
219 }
220 }
221
222 proptest! {
223 #[test]
224 fn test_popcount_incorrect_output_fails(
225 (input, wrong_offset) in prop::sample::select(vec![
226 (0xFFu64, -1i8), (0x0Fu64, 1i8), (0xFFFFu64, -3i8), (0x7u64, 2i8), ])
231 ) {
232 let (circuit, input_wire, output_wire) = build_popcount_circuit();
233 let mut w = circuit.new_witness_filler();
234
235 let correct_count = input.count_ones() as u64;
236 let wrong_count = (correct_count as i64 + wrong_offset as i64) as u64;
241
242 w[input_wire] = Word(input);
243 w[output_wire] = Word(wrong_count);
244
245 let result = circuit.populate_wire_witness(&mut w);
246 assert!(result.is_err(),
247 "Circuit should fail with incorrect count {} instead of {}",
248 wrong_count, correct_count);
249 }
250
251 #[test]
252 fn test_popcount_overflow_fails(
253 overflow_amount in 1u64..100
254 ) {
255 let (circuit, input_wire, output_wire) = build_popcount_circuit();
257 let mut w = circuit.new_witness_filler();
258
259 w[input_wire] = Word(0xFFFFFFFFFFFFFFFF);
260 w[output_wire] = Word(64 + overflow_amount);
261
262 let result = circuit.populate_wire_witness(&mut w);
263 assert!(result.is_err(),
264 "Circuit should fail with popcount = {}", 64 + overflow_amount);
265 }
266 }
267
268 proptest! {
269 #[test]
270 fn test_step2_masking_edge_cases(
271 value in prop::sample::select(vec![
272 0xFFFFFFFFFFFFFFFFu64, 0xCCCCCCCCCCCCCCCCu64, 0xF0F0F0F0F0F0F0F0u64, 0xE0E0E0E0E0E0E0E0u64, ])
277 ) {
278 test_popcount_value(value);
282 }
283 }
284
285 proptest! {
286 #[test]
287 fn test_step3_onwards_no_overflow(
288 pattern in prop::sample::select(vec![
289 0x0F0F0F0F0F0F0F0Fu64, 0x00FF00FF00FF00FFu64, 0x0000FFFF0000FFFFu64, 0x00000000FFFFFFFFu64, 0xFFFF00000000FFFFu64, ])
295 ) {
296 test_popcount_value(pattern);
298 }
299 }
300
301 #[test]
302 fn test_incorrect_step2_would_fail() {
303 let input: u64 = 0xFFFFFFFFFFFFFFFF;
308
309 let step1 = input.wrapping_sub((input >> 1) & 0x5555555555555555);
311 assert_eq!(step1, 0xAAAAAAAAAAAAAAAA, "Step 1 should produce 0xAAAA...");
312
313 let incorrect_sum = step1.wrapping_add(step1 >> 2);
315 let incorrect_step2 = incorrect_sum & 0x3333333333333333;
316
317 let correct_step2 =
319 (step1 & 0x3333333333333333).wrapping_add((step1 >> 2) & 0x3333333333333333);
320
321 assert_ne!(incorrect_step2, correct_step2, "Incorrect Step 2 produces different result!");
323
324 let mut incorrect = incorrect_step2;
327 incorrect = (incorrect.wrapping_add(incorrect >> 4)) & 0x0F0F0F0F0F0F0F0F;
328 incorrect = (incorrect.wrapping_add(incorrect >> 8)) & 0x00FF00FF00FF00FF;
329 incorrect = (incorrect.wrapping_add(incorrect >> 16)) & 0x0000FFFF0000FFFF;
330 incorrect = (incorrect.wrapping_add(incorrect >> 32)) & 0x00000000FFFFFFFF;
331
332 assert_eq!(incorrect, 15, "Incorrect Step 2 leads to wrong count of 15");
334 assert_eq!(input.count_ones() as u64, 64, "Correct count should be 64");
335
336 println!(
339 "Demonstrated: Incorrect Step 2 gives {} instead of {}",
340 incorrect,
341 input.count_ones()
342 );
343 }
344}