1#[cfg(any(feature = "alloc", test))]
2mod rmap;
3#[cfg(test)]
4mod tests;
5pub(super) mod types;
6
7use crate::chiapos::Seed;
8use crate::chiapos::constants::{PARAM_B, PARAM_BC, PARAM_C, PARAM_EXT, PARAM_M};
9#[cfg(feature = "alloc")]
10use crate::chiapos::table::rmap::Rmap;
11use crate::chiapos::table::types::{Metadata, X, Y};
12#[cfg(feature = "alloc")]
13use crate::chiapos::table::types::{Position, R};
14use ab_chacha8::{ChaCha8Block, ChaCha8State};
15#[cfg(feature = "alloc")]
16use ab_core_primitives::pieces::Record;
17#[cfg(feature = "alloc")]
18use alloc::boxed::Box;
19#[cfg(feature = "alloc")]
20use alloc::vec;
21#[cfg(feature = "alloc")]
22use alloc::vec::Vec;
23#[cfg(feature = "alloc")]
24use chacha20::cipher::{Iv, KeyIvInit, StreamCipher};
25#[cfg(feature = "alloc")]
26use chacha20::{ChaCha8, Key};
27use core::array;
28#[cfg(feature = "parallel")]
29use core::cell::SyncUnsafeCell;
30#[cfg(feature = "alloc")]
31use core::mem;
32#[cfg(feature = "alloc")]
33use core::mem::MaybeUninit;
34#[cfg(any(feature = "alloc", test))]
35use core::simd::prelude::*;
36#[cfg(feature = "parallel")]
37use core::sync::atomic::{AtomicUsize, Ordering};
38#[cfg(feature = "alloc")]
39use derive_more::Deref;
40#[cfg(feature = "alloc")]
41use rclite::Arc;
42#[cfg(any(feature = "alloc", test))]
43use seq_macro::seq;
44
45#[cfg(any(feature = "alloc", test))]
46const COMPUTE_F1_SIMD_FACTOR: usize = 8;
47#[cfg(any(feature = "alloc", test))]
48const COMPUTE_FN_SIMD_FACTOR: usize = 16;
49const MAX_BUCKET_SIZE: usize = 512;
50#[cfg(any(feature = "alloc", test))]
51const BUCKET_SIZE_UPPER_BOUND_SECURITY_BITS: u8 = 128;
52const REDUCED_BUCKET_SIZE: usize = 272;
57const REDUCED_MATCHES_COUNT: usize = 288;
62#[cfg(feature = "parallel")]
63const CACHE_LINE_SIZE: usize = 64;
64
65const {
66 debug_assert!(REDUCED_BUCKET_SIZE <= MAX_BUCKET_SIZE);
67 debug_assert!(REDUCED_MATCHES_COUNT <= MAX_BUCKET_SIZE);
68}
69
70const fn y_size_bits(k: u8) -> usize {
72 usize::from(k) + usize::from(PARAM_EXT)
73}
74
75const fn metadata_size_bits(k: u8, table_number: u8) -> usize {
77 usize::from(k)
78 * match table_number {
79 1 => 1,
80 2 => 2,
81 3 | 4 => 4,
82 5 => 3,
83 6 => 2,
84 7 => 0,
85 _ => unreachable!(),
86 }
87}
88
89#[cfg(feature = "alloc")]
91const NUM_BUCKETS<const K: u8>: usize =
92 2_usize
93 .pow(y_size_bits(K) as u32)
94 .div_ceil(usize::from(PARAM_BC));
95#[cfg(feature = "parallel")]
96const NUM_BUCKET_PAIRS<const K: u8>: usize = NUM_BUCKETS::<K> - 1;
97
98#[cfg(feature = "alloc")]
100const MAX_TABLE_SIZE<const K: u8>: usize = 1 << K;
101
102#[cfg(any(feature = "alloc", test))]
103const TABLE_1_YS_BATCH_SIMD<const K: u8>: usize =
104 usize::from(K) * COMPUTE_F1_SIMD_FACTOR / u8::BITS as usize;
105
106#[cfg(feature = "parallel")]
107#[inline(always)]
108fn strip_sync_unsafe_cell<const N: usize, T>(value: Box<[SyncUnsafeCell<T>; N]>) -> Box<[T; N]> {
109 unsafe { Box::from_raw(Box::into_raw(value).cast()) }
111}
112
113#[cfg(feature = "alloc")]
116fn partial_ys<const K: u8>(seed: Seed) -> Vec<u8> {
117 let output_len_bits = usize::from(K) * (1 << K);
118 let mut output = vec![0; output_len_bits.div_ceil(u8::BITS as usize)];
119
120 let key = Key::from(seed);
121 let iv = Iv::<ChaCha8>::default();
122
123 let mut cipher = ChaCha8::new(&key, &iv);
124
125 cipher.write_keystream(&mut output);
126
127 output
128}
129
130#[cfg(feature = "alloc")]
138const fn bucket_size_upper_bound(k: u8, security_bits: u8) -> usize {
139 const LAMBDA: u64 = PARAM_BC as u64 / 2u64.pow(PARAM_EXT as u32);
142 const LN2_NUM: u128 = 693_147;
145 const LN2_DEN: u128 = 1_000_000;
146
147 let ks = k as u128 + security_bits as u128;
149 let num = 3u128 * LAMBDA as u128 * ks * LN2_NUM;
152 let den = LN2_DEN;
154
155 let ceil_div = num.div_ceil(den);
156
157 let mut low = 0u64;
162 let mut high = u64::MAX;
163 while low < high {
164 let mid = low + (high - low) / 2;
165 let left = (mid as u128) * (mid as u128);
166 if left >= ceil_div {
167 high = mid;
168 } else {
169 low = mid + 1;
170 }
171 }
172 let add_term = low;
173
174 (LAMBDA + add_term) as usize
175}
176
177#[cfg(feature = "alloc")]
178fn group_by_buckets<const K: u8>(
179 ys: &[Y],
180) -> Box<[[(Position, Y); REDUCED_BUCKET_SIZE]; NUM_BUCKETS::<K>]> {
181 let mut bucket_offsets = [0_u16; NUM_BUCKETS::<K>];
182 let mut buckets = unsafe {
184 Box::<[[MaybeUninit<(Position, Y)>; REDUCED_BUCKET_SIZE]; NUM_BUCKETS::<K>]>::new_uninit()
185 .assume_init()
186 };
187
188 for (&y, position) in ys.iter().zip(Position::ZERO..) {
189 let bucket_index = (u32::from(y) / u32::from(PARAM_BC)) as usize;
190
191 let bucket_offset = unsafe { bucket_offsets.get_unchecked_mut(bucket_index) };
193 let bucket = unsafe { buckets.get_unchecked_mut(bucket_index) };
195
196 if *bucket_offset < REDUCED_BUCKET_SIZE as u16 {
197 bucket[*bucket_offset as usize].write((position, y));
198 *bucket_offset += 1;
199 }
200 }
201
202 for (bucket, initialized) in buckets.iter_mut().zip(bucket_offsets) {
203 bucket[usize::from(initialized)..].write_filled((Position::SENTINEL, Y::SENTINEL));
204 }
205
206 unsafe { Box::from_raw(Box::into_raw(buckets).cast()) }
208}
209
210#[cfg(feature = "parallel")]
216unsafe fn group_by_buckets_from_buckets<'a, const K: u8, I>(
217 iter: I,
218) -> Box<[[(Position, Y); REDUCED_BUCKET_SIZE]; NUM_BUCKETS::<K>]>
219where
220 I: Iterator<Item = (&'a [MaybeUninit<Y>; REDUCED_MATCHES_COUNT], usize)> + 'a,
221{
222 let mut bucket_offsets = [0_u16; NUM_BUCKETS::<K>];
223 let mut buckets = unsafe {
225 Box::<[[MaybeUninit<(Position, Y)>; REDUCED_BUCKET_SIZE]; NUM_BUCKETS::<K>]>::new_uninit()
226 .assume_init()
227 };
228
229 for ((ys, count), batch_start) in iter.zip((Position::ZERO..).step_by(REDUCED_MATCHES_COUNT)) {
230 let ys = unsafe { ys[..count].assume_init_ref() };
232 for (&y, position) in ys.iter().zip(batch_start..) {
233 let bucket_index = (u32::from(y) / u32::from(PARAM_BC)) as usize;
234
235 let bucket_offset = unsafe { bucket_offsets.get_unchecked_mut(bucket_index) };
237 let bucket = unsafe { buckets.get_unchecked_mut(bucket_index) };
239
240 if *bucket_offset < REDUCED_BUCKET_SIZE as u16 {
241 bucket[*bucket_offset as usize].write((position, y));
242 *bucket_offset += 1;
243 }
244 }
245 }
246
247 for (bucket, initialized) in buckets.iter_mut().zip(bucket_offsets) {
248 bucket[usize::from(initialized)..].write_filled((Position::SENTINEL, Y::SENTINEL));
249 }
250
251 unsafe { Box::from_raw(Box::into_raw(buckets).cast()) }
253}
254
255#[cfg(feature = "alloc")]
256#[derive(Debug, Copy, Clone, Deref)]
257#[repr(align(64))]
258struct CacheLineAligned<T>(T);
259
260#[cfg(feature = "alloc")]
262type LeftTargets =
263 [[CacheLineAligned<[R; const { usize::from(PARAM_M) }]>; const { usize::from(PARAM_BC) }]; 2];
264
265#[cfg(feature = "alloc")]
266fn calculate_left_targets() -> Arc<LeftTargets> {
267 let mut left_targets = Arc::<LeftTargets>::new_uninit();
268 let left_targets_slice = unsafe {
270 mem::transmute::<
271 &mut MaybeUninit<LeftTargets>,
272 &mut [[MaybeUninit<CacheLineAligned<[R; const { usize::from(PARAM_M) }]>>; const {
273 usize::from(PARAM_BC)
274 }]; 2],
275 >(Arc::get_mut_unchecked(&mut left_targets))
276 };
277
278 for parity in 0..=1 {
279 for r in 0..PARAM_BC {
280 let c = r / PARAM_C;
281
282 let arr = array::from_fn(|m| {
283 let m = m as u16;
284 R::from(
285 ((c + m) % PARAM_B) * PARAM_C
286 + (((2 * m + parity) * (2 * m + parity) + r) % PARAM_C),
287 )
288 });
289 left_targets_slice[usize::from(parity)][usize::from(r)].write(CacheLineAligned(arr));
290 }
291 }
292
293 unsafe { left_targets.assume_init() }
295}
296
297fn calculate_left_target_on_demand(parity: u32, r: u32, m: u32) -> u32 {
298 let param_b = u32::from(PARAM_B);
299 let param_c = u32::from(PARAM_C);
300
301 ((r / param_c + m) % param_b) * param_c + (((2 * m + parity) * (2 * m + parity) + r) % param_c)
302}
303
304#[cfg(feature = "alloc")]
306#[derive(Debug, Clone)]
307pub struct TablesCache {
308 left_targets: Arc<LeftTargets>,
309}
310
311#[cfg(feature = "alloc")]
312impl Default for TablesCache {
313 fn default() -> Self {
315 Self {
316 left_targets: calculate_left_targets(),
317 }
318 }
319}
320
321#[cfg(feature = "alloc")]
322#[derive(Debug, Copy, Clone)]
323struct Match {
324 left_position: Position,
325 left_y: Y,
326 right_position: Position,
327}
328
329pub(super) fn compute_f1<const K: u8>(x: X, seed: &Seed) -> Y {
331 const U32S_PER_BLOCK: usize = size_of::<ChaCha8Block>() / size_of::<u32>();
332
333 let skip_bits = u32::from(K) * u32::from(x);
334 let skip_u32s = skip_bits / u32::BITS;
335 let partial_y_offset = skip_bits % u32::BITS;
336
337 let initial_state = ChaCha8State::init(seed, &[0; _]);
338 let first_block_counter = skip_u32s / U32S_PER_BLOCK as u32;
339 let u32_in_first_block = skip_u32s as usize % U32S_PER_BLOCK;
340
341 let first_block = initial_state.compute_block(first_block_counter);
342 let hi = first_block[u32_in_first_block].to_be();
343
344 let lo = if u32_in_first_block + 1 == U32S_PER_BLOCK {
346 let second_block = initial_state.compute_block(first_block_counter + 1);
348 second_block[0].to_be()
349 } else {
350 first_block[u32_in_first_block + 1].to_be()
351 };
352
353 let partial_y = (u64::from(hi) << u32::BITS) | u64::from(lo);
354
355 let pre_y = partial_y >> (u64::BITS - u32::from(K + PARAM_EXT) - partial_y_offset);
356 let pre_y = pre_y as u32;
357 let pre_y_mask = (u32::MAX << PARAM_EXT) & (u32::MAX >> (u32::BITS - u32::from(K + PARAM_EXT)));
359
360 let pre_ext = u32::from(x) >> (K - PARAM_EXT);
363
364 Y::from((pre_y & pre_y_mask) | pre_ext)
367}
368
369#[cfg(any(feature = "alloc", test))]
370pub(super) fn compute_f1_simd<const K: u8>(
371 xs: Simd<u32, COMPUTE_F1_SIMD_FACTOR>,
372 partial_ys: &[u8; TABLE_1_YS_BATCH_SIMD::<K>],
373) -> [Y; COMPUTE_F1_SIMD_FACTOR] {
374 let pre_ys_bytes = array::from_fn(|i| {
377 let partial_y_offset = i * usize::from(K);
378 let partial_y_length =
379 (partial_y_offset % u8::BITS as usize + usize::from(K)).div_ceil(u8::BITS as usize);
380 let mut pre_y_bytes = 0u64.to_be_bytes();
381 pre_y_bytes[..partial_y_length].copy_from_slice(
382 &partial_ys[partial_y_offset / u8::BITS as usize..][..partial_y_length],
383 );
384
385 u64::from_be_bytes(pre_y_bytes)
386 });
387 let pre_ys_right_offset = array::from_fn(|i| {
388 let partial_y_offset = i as u32 * u32::from(K);
389 u64::from(u64::BITS - u32::from(K + PARAM_EXT) - partial_y_offset % u8::BITS)
390 });
391 let pre_ys = Simd::from_array(pre_ys_bytes) >> Simd::from_array(pre_ys_right_offset);
392
393 let pre_ys_mask = Simd::splat(
395 (u32::MAX << usize::from(PARAM_EXT))
396 & (u32::MAX >> (u32::BITS as usize - usize::from(K + PARAM_EXT))),
397 );
398
399 let pre_exts = xs >> Simd::splat(u32::from(K - PARAM_EXT));
402
403 let ys = (pre_ys.cast() & pre_ys_mask) | pre_exts;
406
407 Y::array_from_repr(ys.to_array())
408}
409
410#[cfg(feature = "alloc")]
417unsafe fn find_matches_in_buckets<'a>(
418 left_bucket_index: u32,
419 left_bucket: &[(Position, Y); REDUCED_BUCKET_SIZE],
420 right_bucket: &[(Position, Y); REDUCED_BUCKET_SIZE],
421 matches: &'a mut [MaybeUninit<Match>; REDUCED_MATCHES_COUNT + usize::from(PARAM_M) * 2],
424 left_targets: &LeftTargets,
425) -> &'a [Match] {
426 let left_base = left_bucket_index * u32::from(PARAM_BC);
427 let right_base = left_base + u32::from(PARAM_BC);
428
429 let mut rmap = Rmap::new();
430 for &(right_position, y) in right_bucket {
431 if right_position == Position::SENTINEL {
433 break;
434 }
435 let r = R::from((u32::from(y) - right_base) as u16);
436 unsafe {
439 rmap.add(r, right_position);
440 }
441 }
442
443 let parity = left_base % 2;
444 let left_targets_parity = &left_targets[parity as usize];
445 let mut next_match_index = 0;
446
447 for &(left_position, y) in left_bucket {
450 if left_position == Position::SENTINEL || next_match_index >= REDUCED_MATCHES_COUNT {
453 break;
455 }
456
457 let r = R::from((u32::from(y) - left_base) as u16);
458 let left_targets_r = unsafe { left_targets_parity.get_unchecked(usize::from(r)) };
460
461 for &r_target in left_targets_r.iter() {
462 let [right_position_a, right_position_b] = unsafe { rmap.get(r_target) };
464
465 if right_position_a != Position::SENTINEL {
466 unsafe { matches.get_unchecked_mut(next_match_index) }.write(Match {
469 left_position,
470 left_y: y,
471 right_position: right_position_a,
472 });
473 next_match_index += 1;
474
475 if right_position_b != Position::SENTINEL {
476 unsafe { matches.get_unchecked_mut(next_match_index) }.write(Match {
479 left_position,
480 left_y: y,
481 right_position: right_position_b,
482 });
483 next_match_index += 1;
484 }
485 }
486 }
487 }
488
489 unsafe { matches[..next_match_index].assume_init_ref() }
491}
492
493pub(super) fn has_match(left_y: Y, right_y: Y) -> bool {
495 let right_r = u32::from(right_y) % u32::from(PARAM_BC);
496 let parity = (u32::from(left_y) / u32::from(PARAM_BC)) % 2;
497 let left_r = u32::from(left_y) % u32::from(PARAM_BC);
498
499 let r_targets = array::from_fn::<_, const { usize::from(PARAM_M) }, _>(|i| {
500 calculate_left_target_on_demand(parity, left_r, i as u32)
501 });
502
503 r_targets.contains(&right_r)
504}
505
506#[inline(always)]
507pub(super) fn compute_fn<const K: u8, const TABLE_NUMBER: u8, const PARENT_TABLE_NUMBER: u8>(
508 y: Y,
509 left_metadata: Metadata<K, PARENT_TABLE_NUMBER>,
510 right_metadata: Metadata<K, PARENT_TABLE_NUMBER>,
511) -> (Y, Metadata<K, TABLE_NUMBER>) {
512 let left_metadata = u128::from(left_metadata);
513 let right_metadata = u128::from(right_metadata);
514
515 let parent_metadata_bits = metadata_size_bits(K, PARENT_TABLE_NUMBER);
516
517 let y_and_left_bits = y_size_bits(K) + parent_metadata_bits;
519 let right_bits_start_offset = u128::BITS as usize - parent_metadata_bits;
520
521 let num_bytes_with_data =
523 (y_size_bits(K) + parent_metadata_bits * 2).div_ceil(u8::BITS as usize);
524
525 let hash = {
528 let y_bits = u128::from(y) << (u128::BITS as usize - y_size_bits(K));
530
531 let left_metadata_bits =
533 left_metadata << (u128::BITS as usize - parent_metadata_bits - y_size_bits(K));
534
535 if right_bits_start_offset < y_and_left_bits {
538 let right_bits_pushed_into_input_b = y_and_left_bits - right_bits_start_offset;
539 let right_bits_a = right_metadata >> right_bits_pushed_into_input_b;
542 let input_a = y_bits | left_metadata_bits | right_bits_a;
543 let input_b = right_metadata << (u128::BITS as usize - right_bits_pushed_into_input_b);
545
546 let input = [input_a.to_be_bytes(), input_b.to_be_bytes()];
547 let input_len =
548 size_of::<u128>() + right_bits_pushed_into_input_b.div_ceil(u8::BITS as usize);
549 ab_blake3::single_block_hash(&input.as_flattened()[..input_len])
550 .expect("Exactly a single block worth of bytes; qed")
551 } else {
552 let right_bits_a = right_metadata << (right_bits_start_offset - y_and_left_bits);
553 let input_a = y_bits | left_metadata_bits | right_bits_a;
554
555 ab_blake3::single_block_hash(&input_a.to_be_bytes()[..num_bytes_with_data])
556 .expect("Less than a single block worth of bytes; qed")
557 }
558 };
559
560 let y_output = Y::from(
561 u32::from_be_bytes([hash[0], hash[1], hash[2], hash[3]])
562 >> (u32::BITS as usize - y_size_bits(K)),
563 );
564
565 let metadata_size_bits = metadata_size_bits(K, TABLE_NUMBER);
566
567 let metadata = if TABLE_NUMBER < 4 {
568 (left_metadata << parent_metadata_bits) | right_metadata
569 } else if metadata_size_bits > 0 {
570 let metadata = u128::from_be_bytes(
574 hash[y_size_bits(K) / u8::BITS as usize..][..size_of::<u128>()]
575 .try_into()
576 .expect("Always enough bits for any K; qed"),
577 );
578 let metadata = metadata << (y_size_bits(K) % u8::BITS as usize);
580 metadata >> (u128::BITS as usize - metadata_size_bits)
582 } else {
583 0
584 };
585
586 (y_output, Metadata::from(metadata))
587}
588
589#[cfg(any(feature = "alloc", test))]
593fn compute_fn_simd<const K: u8, const TABLE_NUMBER: u8, const PARENT_TABLE_NUMBER: u8>(
594 left_ys: [Y; COMPUTE_FN_SIMD_FACTOR],
595 left_metadatas: [Metadata<K, PARENT_TABLE_NUMBER>; COMPUTE_FN_SIMD_FACTOR],
596 right_metadatas: [Metadata<K, PARENT_TABLE_NUMBER>; COMPUTE_FN_SIMD_FACTOR],
597) -> (
598 Simd<u32, COMPUTE_FN_SIMD_FACTOR>,
599 [Metadata<K, TABLE_NUMBER>; COMPUTE_FN_SIMD_FACTOR],
600) {
601 let parent_metadata_bits = metadata_size_bits(K, PARENT_TABLE_NUMBER);
602 let metadata_size_bits = metadata_size_bits(K, TABLE_NUMBER);
603
604 let left_metadatas: [u128; COMPUTE_FN_SIMD_FACTOR] = seq!(N in 0..16 {
607 [
608 #(
609 u128::from(left_metadatas[N]),
610 )*
611 ]
612 });
613 let right_metadatas: [u128; COMPUTE_FN_SIMD_FACTOR] = seq!(N in 0..16 {
614 [
615 #(
616 u128::from(right_metadatas[N]),
617 )*
618 ]
619 });
620
621 let y_and_left_bits = y_size_bits(K) + parent_metadata_bits;
623 let right_bits_start_offset = u128::BITS as usize - parent_metadata_bits;
624
625 let num_bytes_with_data =
627 (y_size_bits(K) + parent_metadata_bits * 2).div_ceil(u8::BITS as usize);
628
629 let hashes: [_; COMPUTE_FN_SIMD_FACTOR] = seq!(N in 0..16 {
634 [
635 #(
636 {
637 let y = left_ys[N];
638 let left_metadata = left_metadatas[N];
639 let right_metadata = right_metadatas[N];
640
641 let y_bits = u128::from(y) << (u128::BITS as usize - y_size_bits(K));
644
645 let left_metadata_bits =
647 left_metadata << (u128::BITS as usize - parent_metadata_bits - y_size_bits(K));
648
649 if right_bits_start_offset < y_and_left_bits {
652 let right_bits_pushed_into_input_b = y_and_left_bits - right_bits_start_offset;
653 let right_bits_a = right_metadata >> right_bits_pushed_into_input_b;
656 let input_a = y_bits | left_metadata_bits | right_bits_a;
657 let input_b = right_metadata << (u128::BITS as usize - right_bits_pushed_into_input_b);
659
660 let input = [input_a.to_be_bytes(), input_b.to_be_bytes()];
661 let input_len =
662 size_of::<u128>() + right_bits_pushed_into_input_b.div_ceil(u8::BITS as usize);
663 ab_blake3::single_block_hash(&input.as_flattened()[..input_len])
664 .expect("Exactly a single block worth of bytes; qed")
665 } else {
666 let right_bits_a = right_metadata << (right_bits_start_offset - y_and_left_bits);
667 let input_a = y_bits | left_metadata_bits | right_bits_a;
668
669 ab_blake3::single_block_hash(&input_a.to_be_bytes()[..num_bytes_with_data])
670 .expect("Exactly a single block worth of bytes; qed")
671 }
672 },
673 )*
674 ]
675 });
676
677 let y_outputs = Simd::from_array(
678 hashes.map(|hash| u32::from_be_bytes([hash[0], hash[1], hash[2], hash[3]])),
679 ) >> (u32::BITS - y_size_bits(K) as u32);
680
681 let metadatas = if TABLE_NUMBER < 4 {
682 seq!(N in 0..16 {
683 [
684 #(
685 Metadata::from((left_metadatas[N] << parent_metadata_bits) | right_metadatas[N]),
686 )*
687 ]
688 })
689 } else if metadata_size_bits > 0 {
690 seq!(N in 0..16 {
694 [
695 #(
696 {
697 let metadata = u128::from_be_bytes(
698 hashes[N][y_size_bits(K) / u8::BITS as usize..][..size_of::<u128>()]
699 .try_into()
700 .expect("Always enough bits for any K; qed"),
701 );
702 let metadata = metadata << (y_size_bits(K) % u8::BITS as usize);
704 Metadata::from(metadata >> (u128::BITS as usize - metadata_size_bits))
706 },
707 )*
708 ]
709 })
710 } else {
711 [Metadata::default(); _]
712 };
713
714 (y_outputs, metadatas)
715}
716
717#[cfg(feature = "alloc")]
720#[inline(always)]
721unsafe fn match_to_result<const K: u8, const TABLE_NUMBER: u8, const PARENT_TABLE_NUMBER: u8>(
722 parent_table: &Table<K, PARENT_TABLE_NUMBER>,
723 m: &Match,
724) -> (Y, [Position; 2], Metadata<K, TABLE_NUMBER>)
725where
726 Table<K, PARENT_TABLE_NUMBER>: NotLastTable,
727{
728 let left_metadata = unsafe { parent_table.metadata(m.left_position) };
730 let right_metadata = unsafe { parent_table.metadata(m.right_position) };
732
733 let (y, metadata) =
734 compute_fn::<K, TABLE_NUMBER, PARENT_TABLE_NUMBER>(m.left_y, left_metadata, right_metadata);
735
736 (y, [m.left_position, m.right_position], metadata)
737}
738
739#[cfg(feature = "alloc")]
742#[inline(always)]
743unsafe fn match_to_result_simd<const K: u8, const TABLE_NUMBER: u8, const PARENT_TABLE_NUMBER: u8>(
744 parent_table: &Table<K, PARENT_TABLE_NUMBER>,
745 matches: &[Match; COMPUTE_FN_SIMD_FACTOR],
746) -> (
747 Simd<u32, COMPUTE_FN_SIMD_FACTOR>,
748 [[Position; 2]; COMPUTE_FN_SIMD_FACTOR],
749 [Metadata<K, TABLE_NUMBER>; COMPUTE_FN_SIMD_FACTOR],
750)
751where
752 Table<K, PARENT_TABLE_NUMBER>: NotLastTable,
753{
754 let left_ys: [_; COMPUTE_FN_SIMD_FACTOR] = seq!(N in 0..16 {
755 [
756 #(
757 matches[N].left_y,
758 )*
759 ]
760 });
761 let left_metadatas: [_; COMPUTE_FN_SIMD_FACTOR] = unsafe {
763 seq!(N in 0..16 {
764 [
765 #(
766 parent_table.metadata(matches[N].left_position),
767 )*
768 ]
769 })
770 };
771 let right_metadatas: [_; COMPUTE_FN_SIMD_FACTOR] = unsafe {
773 seq!(N in 0..16 {
774 [
775 #(
776 parent_table.metadata(matches[N].right_position),
777 )*
778 ]
779 })
780 };
781
782 let (y_outputs, metadatas) = compute_fn_simd::<K, TABLE_NUMBER, PARENT_TABLE_NUMBER>(
783 left_ys,
784 left_metadatas,
785 right_metadatas,
786 );
787
788 let positions = seq!(N in 0..16 {
789 [
790 #(
791 [
792 matches[N].left_position,
793 matches[N].right_position,
794 ],
795 )*
796 ]
797 });
798
799 (y_outputs, positions, metadatas)
800}
801
802#[cfg(feature = "alloc")]
806#[inline(always)]
807unsafe fn matches_to_results<const K: u8, const TABLE_NUMBER: u8, const PARENT_TABLE_NUMBER: u8>(
808 parent_table: &Table<K, PARENT_TABLE_NUMBER>,
809 matches: &[Match],
810 ys: &mut [MaybeUninit<Y>],
811 positions: &mut [MaybeUninit<[Position; 2]>],
812 metadatas: &mut [MaybeUninit<Metadata<K, TABLE_NUMBER>>],
813) where
814 Table<K, PARENT_TABLE_NUMBER>: NotLastTable,
815{
816 let (grouped_matches, other_matches) = matches.as_chunks::<COMPUTE_FN_SIMD_FACTOR>();
817 let (grouped_ys, other_ys) = ys.split_at_mut(grouped_matches.as_flattened().len());
818 let grouped_ys = grouped_ys.as_chunks_mut::<COMPUTE_FN_SIMD_FACTOR>().0;
819 let (grouped_positions, other_positions) =
820 positions.split_at_mut(grouped_matches.as_flattened().len());
821 let grouped_positions = grouped_positions
822 .as_chunks_mut::<COMPUTE_FN_SIMD_FACTOR>()
823 .0;
824 let (grouped_metadatas, other_metadatas) =
825 metadatas.split_at_mut(grouped_matches.as_flattened().len());
826 let grouped_metadatas = grouped_metadatas
827 .as_chunks_mut::<COMPUTE_FN_SIMD_FACTOR>()
828 .0;
829
830 for (((grouped_matches, grouped_ys), grouped_positions), grouped_metadatas) in grouped_matches
831 .iter()
832 .zip(grouped_ys)
833 .zip(grouped_positions)
834 .zip(grouped_metadatas)
835 {
836 let (ys_group, positions_group, metadatas_group) =
838 unsafe { match_to_result_simd(parent_table, grouped_matches) };
839 let ys_group = Y::array_from_repr(ys_group.to_array());
840 grouped_ys.write_copy_of_slice(&ys_group);
841 grouped_positions.write_copy_of_slice(&positions_group);
842
843 if metadata_size_bits(K, TABLE_NUMBER) > 0 {
845 grouped_metadatas.write_copy_of_slice(&metadatas_group);
846 }
847 }
848 for (((other_match, other_y), other_positions), other_metadata) in other_matches
849 .iter()
850 .zip(other_ys)
851 .zip(other_positions)
852 .zip(other_metadatas)
853 {
854 let (y, p, metadata) = unsafe { match_to_result(parent_table, other_match) };
856 other_y.write(y);
857 other_positions.write(p);
858 if metadata_size_bits(K, TABLE_NUMBER) > 0 {
860 other_metadata.write(metadata);
861 }
862 }
863}
864
865#[cfg(feature = "alloc")]
867#[derive(Debug)]
868pub(super) enum PrunedTable<const K: u8, const TABLE_NUMBER: u8> {
869 First,
870 Other {
872 positions: Box<[MaybeUninit<[Position; 2]>; MAX_TABLE_SIZE::<K>]>,
874 },
875 #[cfg(feature = "parallel")]
877 OtherBuckets {
878 positions:
882 Box<[[MaybeUninit<[Position; 2]>; REDUCED_MATCHES_COUNT]; NUM_BUCKET_PAIRS::<K>]>,
883 },
884}
885
886#[cfg(feature = "alloc")]
887impl<const K: u8, const TABLE_NUMBER: u8> PrunedTable<K, TABLE_NUMBER> {
888 #[inline(always)]
895 pub(super) unsafe fn position(&self, position: Position) -> [Position; 2] {
896 match self {
897 Self::First => {
898 unreachable!("Not the first table");
899 }
900 Self::Other { positions } => {
901 unsafe { positions.get_unchecked(usize::from(position)).assume_init() }
903 }
904 #[cfg(feature = "parallel")]
905 Self::OtherBuckets { positions } => {
906 unsafe {
908 positions
909 .as_flattened()
910 .get_unchecked(usize::from(position))
911 .assume_init()
912 }
913 }
914 }
915 }
916}
917
918#[cfg(feature = "alloc")]
919#[derive(Debug)]
920pub(super) enum Table<const K: u8, const TABLE_NUMBER: u8> {
921 First {
923 buckets: Box<[[(Position, Y); REDUCED_BUCKET_SIZE]; NUM_BUCKETS::<K>]>,
927 },
928 Other {
930 positions: Box<[MaybeUninit<[Position; 2]>; MAX_TABLE_SIZE::<K>]>,
932 metadatas: Box<[MaybeUninit<Metadata<K, TABLE_NUMBER>>; MAX_TABLE_SIZE::<K>]>,
934 buckets: Box<[[(Position, Y); REDUCED_BUCKET_SIZE]; NUM_BUCKETS::<K>]>,
938 },
939 #[cfg(feature = "parallel")]
941 OtherBuckets {
942 positions:
946 Box<[[MaybeUninit<[Position; 2]>; REDUCED_MATCHES_COUNT]; NUM_BUCKET_PAIRS::<K>]>,
947 metadatas: Box<
951 [[MaybeUninit<Metadata<K, TABLE_NUMBER>>; REDUCED_MATCHES_COUNT];
952 NUM_BUCKET_PAIRS::<K>],
953 >,
954 buckets: Box<[[(Position, Y); REDUCED_BUCKET_SIZE]; NUM_BUCKETS::<K>]>,
958 },
959}
960
961#[cfg(feature = "alloc")]
962impl<const K: u8> Table<K, 1> {
963 pub(super) fn create(seed: Seed) -> Self {
965 debug_assert!(
968 MAX_BUCKET_SIZE >= bucket_size_upper_bound(K, BUCKET_SIZE_UPPER_BOUND_SECURITY_BITS),
969 "Max bucket size is not sufficiently large"
970 );
971
972 let partial_ys = partial_ys::<K>(seed);
973
974 let mut ys =
976 unsafe { Box::<[MaybeUninit<Y>; MAX_TABLE_SIZE::<K>]>::new_uninit().assume_init() };
977
978 for ((ys, xs_batch_start), partial_ys) in ys
979 .as_chunks_mut::<COMPUTE_F1_SIMD_FACTOR>()
980 .0
981 .iter_mut()
982 .zip((X::ZERO..).step_by(COMPUTE_F1_SIMD_FACTOR))
983 .zip(partial_ys.as_chunks::<{ TABLE_1_YS_BATCH_SIMD::<K> }>().0)
984 {
985 let xs = Simd::splat(u32::from(xs_batch_start))
986 + Simd::from_array(array::from_fn(|i| i as u32));
987 let ys_batch = compute_f1_simd::<K>(xs, partial_ys);
988
989 ys.write_copy_of_slice(&ys_batch);
990 }
991
992 let ys = unsafe { ys.assume_init_ref() };
994
995 let buckets = group_by_buckets::<K>(ys);
997
998 Self::First { buckets }
999 }
1000
1001 #[cfg(feature = "parallel")]
1003 pub(super) fn create_parallel(seed: Seed) -> Self {
1004 debug_assert!(
1007 MAX_BUCKET_SIZE >= bucket_size_upper_bound(K, BUCKET_SIZE_UPPER_BOUND_SECURITY_BITS),
1008 "Max bucket size is not sufficiently large"
1009 );
1010
1011 let partial_ys = partial_ys::<K>(seed);
1012
1013 let mut ys =
1015 unsafe { Box::<[MaybeUninit<Y>; MAX_TABLE_SIZE::<K>]>::new_uninit().assume_init() };
1016
1017 for ((ys, xs_batch_start), partial_ys) in ys
1019 .as_chunks_mut::<COMPUTE_F1_SIMD_FACTOR>()
1020 .0
1021 .iter_mut()
1022 .zip((X::ZERO..).step_by(COMPUTE_F1_SIMD_FACTOR))
1023 .zip(partial_ys.as_chunks::<{ TABLE_1_YS_BATCH_SIMD::<K> }>().0)
1024 {
1025 let xs = Simd::splat(u32::from(xs_batch_start))
1026 + Simd::from_array(array::from_fn(|i| i as u32));
1027 let ys_batch = compute_f1_simd::<K>(xs, partial_ys);
1028
1029 ys.write_copy_of_slice(&ys_batch);
1030 }
1031
1032 let ys = unsafe { ys.assume_init_ref() };
1034
1035 let buckets = group_by_buckets::<K>(ys);
1037
1038 Self::First { buckets }
1039 }
1040}
1041
1042#[cfg(feature = "alloc")]
1043pub(super) impl(self) trait SupportedOtherTables {}
1044
1045#[cfg(feature = "alloc")]
1046impl<const K: u8> SupportedOtherTables for Table<K, 2> {}
1047#[cfg(feature = "alloc")]
1048impl<const K: u8> SupportedOtherTables for Table<K, 3> {}
1049#[cfg(feature = "alloc")]
1050impl<const K: u8> SupportedOtherTables for Table<K, 4> {}
1051#[cfg(feature = "alloc")]
1052impl<const K: u8> SupportedOtherTables for Table<K, 5> {}
1053#[cfg(feature = "alloc")]
1054impl<const K: u8> SupportedOtherTables for Table<K, 6> {}
1055#[cfg(feature = "alloc")]
1056impl<const K: u8> SupportedOtherTables for Table<K, 7> {}
1057
1058#[cfg(feature = "alloc")]
1059pub(super) impl(self) trait NotLastTable {}
1060
1061#[cfg(feature = "alloc")]
1062impl<const K: u8> NotLastTable for Table<K, 1> {}
1063#[cfg(feature = "alloc")]
1064impl<const K: u8> NotLastTable for Table<K, 2> {}
1065#[cfg(feature = "alloc")]
1066impl<const K: u8> NotLastTable for Table<K, 3> {}
1067#[cfg(feature = "alloc")]
1068impl<const K: u8> NotLastTable for Table<K, 4> {}
1069#[cfg(feature = "alloc")]
1070impl<const K: u8> NotLastTable for Table<K, 5> {}
1071#[cfg(feature = "alloc")]
1072impl<const K: u8> NotLastTable for Table<K, 6> {}
1073
1074#[cfg(feature = "alloc")]
1075impl<const K: u8, const TABLE_NUMBER: u8> Table<K, TABLE_NUMBER>
1076where
1077 Self: SupportedOtherTables,
1078{
1079 pub(super) fn create<const PARENT_TABLE_NUMBER: u8>(
1083 parent_table: Table<K, PARENT_TABLE_NUMBER>,
1084 cache: &TablesCache,
1085 ) -> (Self, PrunedTable<K, PARENT_TABLE_NUMBER>)
1086 where
1087 Table<K, PARENT_TABLE_NUMBER>: NotLastTable,
1088 {
1089 let left_targets = &*cache.left_targets;
1090 let mut initialized_elements = 0_usize;
1091 let mut ys =
1093 unsafe { Box::<[MaybeUninit<Y>; MAX_TABLE_SIZE::<K>]>::new_uninit().assume_init() };
1094 let mut positions = unsafe {
1096 Box::<[MaybeUninit<[Position; 2]>; MAX_TABLE_SIZE::<K>]>::new_uninit().assume_init()
1097 };
1098 let mut metadatas = unsafe {
1100 Box::<[MaybeUninit<Metadata<K, TABLE_NUMBER>>; MAX_TABLE_SIZE::<K>]>::new_uninit()
1101 .assume_init()
1102 };
1103
1104 for ([left_bucket, right_bucket], left_bucket_index) in
1105 parent_table.buckets().array_windows().zip(0..)
1106 {
1107 let mut matches = [MaybeUninit::uninit(); _];
1108 let matches = unsafe {
1111 find_matches_in_buckets(
1112 left_bucket_index,
1113 left_bucket,
1114 right_bucket,
1115 &mut matches,
1116 left_targets,
1117 )
1118 };
1119 let matches = &matches[..matches.len().min(REDUCED_MATCHES_COUNT)];
1121 let (ys, positions, metadatas) = unsafe {
1123 (
1124 ys.get_unchecked_mut(initialized_elements..),
1125 positions.get_unchecked_mut(initialized_elements..),
1126 metadatas.get_unchecked_mut(initialized_elements..),
1127 )
1128 };
1129
1130 let (ys, positions, metadatas) = unsafe {
1132 (
1133 ys.get_unchecked_mut(..matches.len()),
1134 positions.get_unchecked_mut(..matches.len()),
1135 metadatas.get_unchecked_mut(..matches.len()),
1136 )
1137 };
1138
1139 unsafe {
1142 matches_to_results(&parent_table, matches, ys, positions, metadatas);
1143 }
1144
1145 initialized_elements += matches.len();
1146 }
1147
1148 let parent_table = parent_table.prune();
1149
1150 let ys = unsafe {
1153 let ys_len = ys.len();
1154 let ys = Box::into_raw(ys);
1155 Vec::from_raw_parts(ys.cast(), initialized_elements, ys_len)
1156 };
1157
1158 let buckets = group_by_buckets::<K>(&ys);
1160
1161 let table = Self::Other {
1162 positions,
1163 metadatas,
1164 buckets,
1165 };
1166
1167 (table, parent_table)
1168 }
1169
1170 #[cfg(feature = "parallel")]
1174 pub(super) fn create_parallel<const PARENT_TABLE_NUMBER: u8>(
1175 parent_table: Table<K, PARENT_TABLE_NUMBER>,
1176 cache: &TablesCache,
1177 ) -> (Self, PrunedTable<K, PARENT_TABLE_NUMBER>)
1178 where
1179 Table<K, PARENT_TABLE_NUMBER>: NotLastTable,
1180 {
1181 let ys = unsafe {
1183 Box::<[SyncUnsafeCell<[MaybeUninit<_>; REDUCED_MATCHES_COUNT]>; NUM_BUCKET_PAIRS::<K>]>::new_uninit().assume_init()
1184 };
1185 let positions = unsafe {
1187 Box::<[SyncUnsafeCell<[MaybeUninit<_>; REDUCED_MATCHES_COUNT]>; NUM_BUCKET_PAIRS::<K>]>::new_uninit().assume_init()
1188 };
1189 let metadatas = unsafe {
1191 Box::<[SyncUnsafeCell<[MaybeUninit<_>; REDUCED_MATCHES_COUNT]>; NUM_BUCKET_PAIRS::<K>]>::new_uninit().assume_init()
1192 };
1193 let global_results_counts =
1194 array::from_fn::<_, { NUM_BUCKET_PAIRS::<K> }, _>(|_| SyncUnsafeCell::new(0u16));
1195
1196 let left_targets = &*cache.left_targets;
1197
1198 let buckets = parent_table.buckets();
1199 let bucket_batch_size = CACHE_LINE_SIZE / size_of::<u16>();
1202 let bucket_batch_index = AtomicUsize::new(0);
1203
1204 rayon::broadcast(|_ctx| {
1205 loop {
1206 let bucket_batch_index = bucket_batch_index.fetch_add(1, Ordering::Relaxed);
1207
1208 let buckets_batch = buckets
1209 .array_windows()
1210 .enumerate()
1211 .skip(bucket_batch_index * bucket_batch_size)
1212 .take(bucket_batch_size);
1213
1214 if buckets_batch.is_empty() {
1215 break;
1216 }
1217
1218 for (left_bucket_index, [left_bucket, right_bucket]) in buckets_batch {
1219 let mut matches = [MaybeUninit::uninit(); _];
1220 let matches = unsafe {
1223 find_matches_in_buckets(
1224 left_bucket_index as u32,
1225 left_bucket,
1226 right_bucket,
1227 &mut matches,
1228 left_targets,
1229 )
1230 };
1231 let matches = &matches[..matches.len().min(REDUCED_MATCHES_COUNT)];
1233
1234 let ys = unsafe { &mut *ys.get_unchecked(left_bucket_index).get() };
1237 let positions =
1240 unsafe { &mut *positions.get_unchecked(left_bucket_index).get() };
1241 let metadatas =
1244 unsafe { &mut *metadatas.get_unchecked(left_bucket_index).get() };
1245 let count = unsafe {
1248 &mut *global_results_counts.get_unchecked(left_bucket_index).get()
1249 };
1250
1251 unsafe {
1254 matches_to_results::<_, TABLE_NUMBER, _>(
1255 &parent_table,
1256 matches,
1257 ys,
1258 positions,
1259 metadatas,
1260 );
1261 }
1262 *count = matches.len() as u16;
1263 }
1264 }
1265 });
1266
1267 let parent_table = parent_table.prune();
1268
1269 let ys = strip_sync_unsafe_cell(ys);
1270 let positions = strip_sync_unsafe_cell(positions);
1271 let metadatas = strip_sync_unsafe_cell(metadatas);
1272
1273 let buckets = unsafe {
1276 group_by_buckets_from_buckets::<K, _>(
1277 ys.iter().zip(
1278 global_results_counts
1279 .into_iter()
1280 .map(|count| usize::from(count.into_inner())),
1281 ),
1282 )
1283 };
1284
1285 let table = Self::OtherBuckets {
1286 positions,
1287 metadatas,
1288 buckets,
1289 };
1290
1291 (table, parent_table)
1292 }
1293
1294 #[inline(always)]
1301 pub(super) unsafe fn position(&self, position: Position) -> [Position; 2] {
1302 #[expect(
1303 clippy::rest_pattern_accessible_field,
1304 reason = "Do not need other fields"
1305 )]
1306 match self {
1307 Self::First { .. } => {
1308 unreachable!("Not the first table");
1309 }
1310 Self::Other { positions, .. } => {
1311 unsafe { positions.get_unchecked(usize::from(position)).assume_init() }
1313 }
1314 #[cfg(feature = "parallel")]
1315 Self::OtherBuckets { positions, .. } => {
1316 unsafe {
1318 positions
1319 .as_flattened()
1320 .get_unchecked(usize::from(position))
1321 .assume_init()
1322 }
1323 }
1324 }
1325 }
1326}
1327
1328#[cfg(feature = "alloc")]
1329impl<const K: u8> Table<K, 7>
1330where
1331 Self: SupportedOtherTables,
1332{
1333 pub(super) fn create_proof_targets(
1336 parent_table: Table<K, 6>,
1337 cache: &TablesCache,
1338 ) -> (
1339 Box<[[Position; 2]; const { Record::NUM_S_BUCKETS }]>,
1340 PrunedTable<K, 6>,
1341 )
1342 where
1343 Table<K, 6>: NotLastTable,
1344 {
1345 let left_targets = &*cache.left_targets;
1346 let mut table_6_proof_targets = unsafe {
1348 Box::<[[Position; 2]; const { Record::NUM_S_BUCKETS }]>::new_zeroed().assume_init()
1349 };
1350
1351 for ([left_bucket, right_bucket], left_bucket_index) in
1352 parent_table.buckets().array_windows().zip(0..)
1353 {
1354 let mut matches = [MaybeUninit::uninit(); _];
1355 let matches = unsafe {
1358 find_matches_in_buckets(
1359 left_bucket_index,
1360 left_bucket,
1361 right_bucket,
1362 &mut matches,
1363 left_targets,
1364 )
1365 };
1366 let matches = &matches[..matches.len().min(REDUCED_MATCHES_COUNT)];
1368
1369 let (grouped_matches, other_matches) = matches.as_chunks::<COMPUTE_FN_SIMD_FACTOR>();
1370
1371 for grouped_matches in grouped_matches {
1372 let (ys_group, positions_group, _) =
1374 unsafe { match_to_result_simd::<_, 7, _>(&parent_table, grouped_matches) };
1375
1376 let s_buckets = ys_group >> Simd::splat(u32::from(PARAM_EXT));
1377
1378 for (s_bucket, p) in s_buckets.to_array().into_iter().zip(positions_group) {
1379 const {
1380 assert!(Record::NUM_S_BUCKETS == usize::from(u16::MAX) + 1);
1381 }
1382 let Ok(s_bucket) = u16::try_from(s_bucket) else {
1383 continue;
1384 };
1385 let positions = &mut table_6_proof_targets[usize::from(s_bucket)];
1386 if positions == &[Position::ZERO; 2] {
1387 *positions = p;
1388 }
1389 }
1390 }
1391 for other_match in other_matches {
1392 let (y, p, _) = unsafe { match_to_result::<_, 7, _>(&parent_table, other_match) };
1394
1395 let s_bucket = y.first_k_bits();
1396
1397 const {
1398 assert!(Record::NUM_S_BUCKETS == usize::from(u16::MAX) + 1);
1399 }
1400 let Ok(s_bucket) = u16::try_from(s_bucket) else {
1401 continue;
1402 };
1403
1404 let positions = &mut table_6_proof_targets[usize::from(s_bucket)];
1405 if positions == &[Position::ZERO; 2] {
1406 *positions = p;
1407 }
1408 }
1409 }
1410
1411 let parent_table = parent_table.prune();
1412
1413 (table_6_proof_targets, parent_table)
1414 }
1415
1416 #[cfg(feature = "parallel")]
1420 pub(super) fn create_proof_targets_parallel(
1421 parent_table: Table<K, 6>,
1422 cache: &TablesCache,
1423 ) -> (
1424 Box<[[Position; 2]; const { Record::NUM_S_BUCKETS }]>,
1425 PrunedTable<K, 6>,
1426 )
1427 where
1428 Table<K, 6>: NotLastTable,
1429 {
1430 let buckets_positions = unsafe {
1432 Box::<[SyncUnsafeCell<[MaybeUninit<_>; REDUCED_MATCHES_COUNT]>; NUM_BUCKET_PAIRS::<K>]>::new_uninit().assume_init()
1433 };
1434 let global_results_counts =
1435 array::from_fn::<_, { NUM_BUCKET_PAIRS::<K> }, _>(|_| SyncUnsafeCell::new(0u16));
1436
1437 let left_targets = &*cache.left_targets;
1438
1439 let buckets = parent_table.buckets();
1440 let bucket_batch_size = CACHE_LINE_SIZE / size_of::<u16>();
1443 let bucket_batch_index = AtomicUsize::new(0);
1444
1445 rayon::broadcast(|_ctx| {
1446 loop {
1447 let bucket_batch_index = bucket_batch_index.fetch_add(1, Ordering::Relaxed);
1448
1449 let buckets_batch = buckets
1450 .array_windows()
1451 .enumerate()
1452 .skip(bucket_batch_index * bucket_batch_size)
1453 .take(bucket_batch_size);
1454
1455 if buckets_batch.is_empty() {
1456 break;
1457 }
1458
1459 for (left_bucket_index, [left_bucket, right_bucket]) in buckets_batch {
1460 let mut matches = [MaybeUninit::uninit(); _];
1461 let matches = unsafe {
1464 find_matches_in_buckets(
1465 left_bucket_index as u32,
1466 left_bucket,
1467 right_bucket,
1468 &mut matches,
1469 left_targets,
1470 )
1471 };
1472 let matches = &matches[..matches.len().min(REDUCED_MATCHES_COUNT)];
1474
1475 let buckets_positions =
1478 unsafe { &mut *buckets_positions.get_unchecked(left_bucket_index).get() };
1479 let count = unsafe {
1482 &mut *global_results_counts.get_unchecked(left_bucket_index).get()
1483 };
1484
1485 let (grouped_matches, other_matches) =
1486 matches.as_chunks::<COMPUTE_FN_SIMD_FACTOR>();
1487
1488 let mut reduced_count = 0_usize;
1489 for grouped_matches in grouped_matches {
1490 let (ys_group, positions_group, _) = unsafe {
1492 match_to_result_simd::<_, 7, _>(&parent_table, grouped_matches)
1493 };
1494
1495 let s_buckets = ys_group >> Simd::splat(u32::from(PARAM_EXT));
1496 let s_buckets = s_buckets.to_array();
1497
1498 for (s_bucket, p) in s_buckets.into_iter().zip(positions_group) {
1499 const {
1500 assert!(Record::NUM_S_BUCKETS == usize::from(u16::MAX) + 1);
1501 }
1502 let Ok(s_bucket) = u16::try_from(s_bucket) else {
1503 continue;
1504 };
1505
1506 buckets_positions[reduced_count].write((s_bucket, p));
1507 reduced_count += 1;
1508 }
1509 }
1510 for other_match in other_matches {
1511 let (y, p, _) =
1513 unsafe { match_to_result::<_, 7, _>(&parent_table, other_match) };
1514
1515 let s_bucket = y.first_k_bits();
1516
1517 const {
1518 assert!(Record::NUM_S_BUCKETS == usize::from(u16::MAX) + 1);
1519 }
1520 let Ok(s_bucket) = u16::try_from(s_bucket) else {
1521 continue;
1522 };
1523
1524 buckets_positions[reduced_count].write((s_bucket, p));
1525 reduced_count += 1;
1526 }
1527
1528 *count = reduced_count as u16;
1529 }
1530 }
1531 });
1532
1533 let parent_table = parent_table.prune();
1534
1535 let buckets_positions = strip_sync_unsafe_cell(buckets_positions);
1536
1537 let mut table_6_proof_targets = unsafe {
1539 Box::<[[Position; 2]; const { Record::NUM_S_BUCKETS }]>::new_zeroed().assume_init()
1540 };
1541
1542 for (bucket, results_count) in buckets_positions.iter().zip(
1543 global_results_counts
1544 .into_iter()
1545 .map(|count| usize::from(count.into_inner())),
1546 ) {
1547 for &(s_bucket, p) in unsafe { bucket[..results_count].assume_init_ref() } {
1549 let positions = &mut table_6_proof_targets[usize::from(s_bucket)];
1550 if positions == &[Position::ZERO; 2] {
1551 *positions = p;
1552 }
1553 }
1554 }
1555
1556 (table_6_proof_targets, parent_table)
1557 }
1558}
1559
1560#[cfg(feature = "alloc")]
1561impl<const K: u8, const TABLE_NUMBER: u8> Table<K, TABLE_NUMBER>
1562where
1563 Self: NotLastTable,
1564{
1565 #[inline(always)]
1570 unsafe fn metadata(&self, position: Position) -> Metadata<K, TABLE_NUMBER> {
1571 #[expect(
1572 clippy::rest_pattern_accessible_field,
1573 reason = "Do not need other fields"
1574 )]
1575 match self {
1576 Self::First { .. } => {
1577 Metadata::from(X::from(u32::from(position)))
1579 }
1580 Self::Other { metadatas, .. } => {
1581 unsafe { metadatas.get_unchecked(usize::from(position)).assume_init() }
1583 }
1584 #[cfg(feature = "parallel")]
1585 Self::OtherBuckets { metadatas, .. } => {
1586 unsafe {
1588 metadatas
1589 .as_flattened()
1590 .get_unchecked(usize::from(position))
1591 .assume_init()
1592 }
1593 }
1594 }
1595 }
1596}
1597
1598#[cfg(feature = "alloc")]
1599impl<const K: u8, const TABLE_NUMBER: u8> Table<K, TABLE_NUMBER> {
1600 #[inline(always)]
1601 fn prune(self) -> PrunedTable<K, TABLE_NUMBER> {
1602 #[expect(
1603 clippy::rest_pattern_accessible_field,
1604 reason = "Do not need other fields"
1605 )]
1606 match self {
1607 Self::First { .. } => PrunedTable::First,
1608 Self::Other { positions, .. } => PrunedTable::Other { positions },
1609 #[cfg(feature = "parallel")]
1610 Self::OtherBuckets { positions, .. } => PrunedTable::OtherBuckets { positions },
1611 }
1612 }
1613
1614 #[inline(always)]
1616 pub(super) fn buckets(&self) -> &[[(Position, Y); REDUCED_BUCKET_SIZE]; NUM_BUCKETS::<K>] {
1617 #[expect(
1618 clippy::rest_pattern_accessible_field,
1619 reason = "Do not need other fields"
1620 )]
1621 match self {
1622 Self::First { buckets } => buckets,
1623 Self::Other { buckets, .. } => buckets,
1624 #[cfg(feature = "parallel")]
1625 Self::OtherBuckets { buckets, .. } => buckets,
1626 }
1627 }
1628}