1mod constants;
4mod table;
5#[cfg(all(feature = "alloc", test))]
6mod tests;
7
8#[cfg(feature = "alloc")]
9use crate::PosProofs;
10use crate::chiapos::constants::NUM_TABLES;
11#[cfg(feature = "alloc")]
12use crate::chiapos::table::types::Position;
13use crate::chiapos::table::types::{Metadata, X, Y};
14#[cfg(feature = "alloc")]
15use crate::chiapos::table::{PrunedTable, Table};
16use crate::chiapos::table::{compute_f1, compute_fn, has_match};
17#[cfg(feature = "alloc")]
18use ab_core_primitives::pieces::Record;
19#[cfg(feature = "alloc")]
20use ab_core_primitives::pos::PosProof;
21#[cfg(feature = "alloc")]
22use ab_core_primitives::sectors::SBucket;
23#[cfg(feature = "alloc")]
24use alloc::boxed::Box;
25#[cfg(feature = "alloc")]
26use core::mem;
27use core::mem::MaybeUninit;
28#[cfg(feature = "alloc")]
29use core::mem::offset_of;
30use core::{array, hint};
31#[cfg(feature = "parallel")]
32use rayon::prelude::*;
33#[cfg(any(feature = "full-chiapos", test))]
34use sha2::{Digest, Sha256};
35
36pub impl(self) trait SupportedKValue {}
38
39pub const PROOF_SIZE<const K: u8>: usize =
41 2usize.pow(u32::from(NUM_TABLES - 1)) * usize::from(K) / u8::BITS as usize;
42
43#[derive(Debug)]
45#[cfg(feature = "alloc")]
46#[repr(C)]
47pub struct Proofs<const K: u8> {
48 pub mut(self) found_proofs: [u8; Record::NUM_S_BUCKETS / u8::BITS as usize],
56 pub mut(self) proofs: [[u8; PROOF_SIZE::<K>]; const { Record::NUM_CHUNKS }],
58}
59
60#[cfg(feature = "alloc")]
61impl From<Box<Proofs<const { PosProof::K }>>> for Box<PosProofs> {
62 fn from(proofs: Box<Proofs<const { PosProof::K }>>) -> Self {
65 const {
67 assert!(size_of::<Proofs<const { PosProof::K }>>() == size_of::<PosProofs>());
68 assert!(align_of::<Proofs<const { PosProof::K }>>() == align_of::<PosProofs>());
69 assert!(
70 offset_of!(Proofs<const { PosProof::K }>, found_proofs)
71 == offset_of!(PosProofs, found_proofs)
72 );
73 assert!(
74 offset_of!(Proofs<const { PosProof::K }>, proofs) == offset_of!(PosProofs, proofs)
75 );
76 }
77 unsafe { Box::from_raw(Box::into_raw(proofs).cast()) }
79 }
80}
81
82#[cfg(feature = "alloc")]
83impl<const K: u8> Proofs<K> {
84 #[inline]
89 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
90 pub fn for_s_bucket(&self, s_bucket: SBucket) -> Option<[u8; PROOF_SIZE::<K>]> {
91 let proof_index = PosProofs::proof_index_for_s_bucket(&self.found_proofs, s_bucket)?;
92
93 unsafe {
95 hint::assert_unchecked(proof_index < Record::NUM_CHUNKS);
96 }
97
98 Some(self.proofs[proof_index])
99 }
100
101 #[inline(always)]
104 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
105 fn split_uninit(
106 proofs: &mut MaybeUninit<Self>,
107 ) -> (
108 &mut [u8; Record::NUM_S_BUCKETS / u8::BITS as usize],
109 &mut [MaybeUninit<[u8; PROOF_SIZE::<K>]>; const { Record::NUM_CHUNKS }],
110 ) {
111 let proofs_ptr = proofs.as_mut_ptr();
112 let found_proofs = unsafe {
114 (&raw mut (*proofs_ptr).found_proofs)
115 .as_uninit_mut()
116 .expect("Not null; qed")
117 };
118 let found_proofs = found_proofs.write([0; _]);
119 let proofs = unsafe {
121 (&raw mut (*proofs_ptr).proofs)
122 .cast::<[MaybeUninit<_>; const { Record::NUM_CHUNKS }]>()
123 .as_mut_unchecked()
124 };
125
126 (found_proofs, proofs)
127 }
128}
129
130type Seed = [u8; 32];
131#[cfg(any(feature = "full-chiapos", test))]
132type Challenge = [u8; 32];
133#[cfg(any(feature = "full-chiapos", test))]
134type Quality = [u8; 32];
135
136#[cfg(all(feature = "alloc", any(feature = "full-chiapos", test)))]
138const fn pick_position(
139 [left_position, right_position]: [Position; 2],
140 last_5_challenge_bits: u8,
141 table_number: u8,
142) -> Position {
143 if ((last_5_challenge_bits >> (table_number - 2)) & 1) == 0 {
144 left_position
145 } else {
146 right_position
147 }
148}
149
150#[cfg(feature = "alloc")]
152const EXPANDED_POSITIONS<const N: usize>: usize = N * 2;
153
154#[cfg(feature = "alloc")]
156#[inline(always)]
157#[cfg_attr(feature = "no-panic", no_panic::no_panic)]
158fn expand_positions<const N: usize>(
159 positions: [Position; N],
160 expand: impl Fn(Position) -> [Position; 2],
161) -> [Position; EXPANDED_POSITIONS::<N>] {
162 let expanded = positions.map(expand);
163
164 unsafe { mem::transmute_copy(&expanded) }
167}
168
169#[derive(Debug)]
171pub struct Tables<const K: u8>
172where
173 Self: SupportedKValue,
174{
175 #[cfg(feature = "alloc")]
176 table_2: PrunedTable<K, 2>,
177 #[cfg(feature = "alloc")]
178 table_3: PrunedTable<K, 3>,
179 #[cfg(feature = "alloc")]
180 table_4: PrunedTable<K, 4>,
181 #[cfg(feature = "alloc")]
182 table_5: PrunedTable<K, 5>,
183 #[cfg(feature = "alloc")]
184 table_6: PrunedTable<K, 6>,
185 #[cfg(feature = "alloc")]
186 table_7: Table<K, 7>,
187}
188
189impl<const K: u8> Tables<K>
190where
191 Self: SupportedKValue,
192{
193 #[cfg(all(feature = "alloc", any(feature = "full-chiapos", test)))]
198 pub fn create(seed: Seed) -> Self {
199 let table_1 = Table::<K, 1>::create(seed);
200 let (table_2, _) = Table::<K, 2>::create(table_1);
201 let (table_3, table_2) = Table::<K, 3>::create(table_2);
202 let (table_4, table_3) = Table::<K, 4>::create(table_3);
203 let (table_5, table_4) = Table::<K, 5>::create(table_4);
204 let (table_6, table_5) = Table::<K, 6>::create(table_5);
205 let (table_7, table_6) = Table::<K, 7>::create(table_6);
206
207 Self {
208 table_2,
209 table_3,
210 table_4,
211 table_5,
212 table_6,
213 table_7,
214 }
215 }
216
217 #[cfg(feature = "alloc")]
224 pub fn create_proofs(seed: Seed) -> Box<Proofs<K>> {
225 let table_1 = Table::<K, 1>::create(seed);
226 let (table_2, _) = Table::<K, 2>::create(table_1);
227 let (table_3, table_2) = Table::<K, 3>::create(table_2);
228 let (table_4, table_3) = Table::<K, 4>::create(table_3);
229 let (table_5, table_4) = Table::<K, 5>::create(table_4);
230 let (table_6, table_5) = Table::<K, 6>::create(table_5);
231 let (table_6_proof_targets, table_6) = Table::<K, 7>::create_proof_targets(table_6);
232
233 let mut proofs = Box::<Proofs<K>>::new_uninit();
234 Self::find_proofs_internal(
235 &table_2,
236 &table_3,
237 &table_4,
238 &table_5,
239 &table_6,
240 &table_6_proof_targets,
241 &mut proofs,
242 );
243
244 unsafe { proofs.assume_init() }
246 }
247
248 #[cfg(feature = "alloc")]
251 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
252 fn find_proofs_internal(
253 table_2: &PrunedTable<K, 2>,
254 table_3: &PrunedTable<K, 3>,
255 table_4: &PrunedTable<K, 4>,
256 table_5: &PrunedTable<K, 5>,
257 table_6: &PrunedTable<K, 6>,
258 table_6_proof_targets: &[[Position; 2]; const { Record::NUM_S_BUCKETS }],
259 proofs: &mut MaybeUninit<Proofs<K>>,
260 ) {
261 let (found_proofs, proofs) = Proofs::<K>::split_uninit(proofs);
262
263 let mut num_found_proofs = 0_usize;
264 'outer: for (table_6_proof_targets, found_proofs) in table_6_proof_targets
265 .as_chunks::<{ u8::BITS as usize }>()
266 .0
267 .iter()
268 .zip(found_proofs)
269 {
270 for (proof_offset, table_6_proof_targets) in table_6_proof_targets.iter().enumerate() {
271 if table_6_proof_targets != &[Position::ZERO; 2] {
272 let proof = Self::find_proof_raw_internal(
273 table_2,
274 table_3,
275 table_4,
276 table_5,
277 table_6,
278 *table_6_proof_targets,
279 );
280
281 *found_proofs |= 1 << proof_offset;
282
283 unsafe {
286 hint::assert_unchecked(num_found_proofs < Record::NUM_CHUNKS);
287 }
288 proofs[num_found_proofs].write(proof);
289 num_found_proofs += 1;
290
291 if num_found_proofs == Record::NUM_CHUNKS {
292 break 'outer;
293 }
294 }
295 }
296 }
297
298 debug_assert_eq!(num_found_proofs, Record::NUM_CHUNKS);
300 }
301
302 #[cfg(all(feature = "parallel", any(feature = "full-chiapos", test)))]
305 pub fn create_parallel(seed: Seed) -> Self {
306 let table_1 = Table::<K, 1>::create_parallel(seed);
307 let (table_2, _) = Table::<K, 2>::create_parallel(table_1);
308 let (table_3, table_2) = Table::<K, 3>::create_parallel(table_2);
309 let (table_4, table_3) = Table::<K, 4>::create_parallel(table_3);
310 let (table_5, table_4) = Table::<K, 5>::create_parallel(table_4);
311 let (table_6, table_5) = Table::<K, 6>::create_parallel(table_5);
312 let (table_7, table_6) = Table::<K, 7>::create_parallel(table_6);
313
314 Self {
315 table_2,
316 table_3,
317 table_4,
318 table_5,
319 table_6,
320 table_7,
321 }
322 }
323
324 #[cfg(feature = "parallel")]
327 pub fn create_proofs_parallel(seed: Seed) -> Box<Proofs<K>> {
328 let table_1 = Table::<K, 1>::create_parallel(seed);
329 let (table_2, _) = Table::<K, 2>::create_parallel(table_1);
330 let (table_3, table_2) = Table::<K, 3>::create_parallel(table_2);
331 let (table_4, table_3) = Table::<K, 4>::create_parallel(table_3);
332 let (table_5, table_4) = Table::<K, 5>::create_parallel(table_4);
333 let (table_6, table_5) = Table::<K, 6>::create_parallel(table_5);
334 let (table_6_proof_targets, table_6) =
335 Table::<K, 7>::create_proof_targets_parallel(table_6);
336
337 let mut proofs = Box::<Proofs<K>>::new_uninit();
338 let mut targets = unsafe {
340 Box::<[MaybeUninit<[Position; 2]>; const { Record::NUM_CHUNKS }]>::new_uninit()
341 .assume_init()
342 };
343 {
344 let (found_proofs, proofs) = Proofs::<K>::split_uninit(&mut proofs);
345
346 let targets =
349 Self::collect_proof_targets(&table_6_proof_targets, found_proofs, &mut targets);
350
351 proofs[..targets.len()]
354 .par_iter_mut()
355 .zip(targets)
356 .for_each(|(proof, &table_6_proof_targets)| {
357 proof.write(Self::find_proof_raw_internal(
358 &table_2,
359 &table_3,
360 &table_4,
361 &table_5,
362 &table_6,
363 table_6_proof_targets,
364 ));
365 });
366 }
367
368 unsafe { proofs.assume_init() }
370 }
371
372 #[cfg(feature = "parallel")]
375 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
376 fn collect_proof_targets<'a>(
377 table_6_proof_targets: &[[Position; 2]; const { Record::NUM_S_BUCKETS }],
378 found_proofs: &mut [u8; Record::NUM_S_BUCKETS / u8::BITS as usize],
379 targets: &'a mut [MaybeUninit<[Position; 2]>; const { Record::NUM_CHUNKS }],
380 ) -> &'a [[Position; 2]] {
381 let mut num_found_proofs = 0_usize;
382
383 'outer: for (table_6_proof_targets, found_proofs) in table_6_proof_targets
384 .as_chunks::<{ u8::BITS as usize }>()
385 .0
386 .iter()
387 .zip(found_proofs)
388 {
389 for (proof_offset, table_6_proof_targets) in table_6_proof_targets.iter().enumerate() {
390 if table_6_proof_targets != &[Position::ZERO; 2] {
391 *found_proofs |= 1 << proof_offset;
392
393 unsafe {
397 hint::assert_unchecked(num_found_proofs < Record::NUM_CHUNKS);
398 }
399 targets[num_found_proofs].write(*table_6_proof_targets);
400 num_found_proofs += 1;
401
402 if num_found_proofs == Record::NUM_CHUNKS {
403 break 'outer;
404 }
405 }
406 }
407 }
408
409 unsafe {
412 hint::assert_unchecked(num_found_proofs <= Record::NUM_CHUNKS);
413 }
414
415 unsafe { targets[..num_found_proofs].assume_init_ref() }
417 }
418
419 #[cfg(all(feature = "alloc", any(feature = "full-chiapos", test)))]
421 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
422 pub fn find_quality<'a>(
423 &'a self,
424 challenge: &'a Challenge,
425 ) -> impl Iterator<Item = Quality> + 'a {
426 let last_5_challenge_bits = challenge[challenge.len() - 1] & 0b0001_1111;
427
428 let first_k_challenge_bits = u32::from_be_bytes(
429 challenge[..size_of::<u32>()]
430 .try_into()
431 .expect("Challenge is known to statically have enough bytes; qed"),
432 ) >> (u32::BITS as usize - usize::from(K));
433
434 let bucket = unsafe {
436 self.table_7
437 .buckets()
438 .get_unchecked(Y::bucket_range_from_first_k_bits(first_k_challenge_bits))
439 };
440 bucket
442 .iter()
443 .flat_map(move |positions| {
444 positions
445 .iter()
446 .take_while(|&&(position, _y)| position != Position::SENTINEL)
447 .filter(move |&&(_position, y)| y.first_k_bits() == first_k_challenge_bits)
448 })
449 .map(move |&(position, _y)| {
450 let positions = unsafe { self.table_7.position(position) };
452 let positions = unsafe {
454 self.table_6
455 .position(pick_position(positions, last_5_challenge_bits, 6))
456 };
457 let positions = unsafe {
459 self.table_5
460 .position(pick_position(positions, last_5_challenge_bits, 5))
461 };
462 let positions = unsafe {
464 self.table_4
465 .position(pick_position(positions, last_5_challenge_bits, 4))
466 };
467 let positions = unsafe {
469 self.table_3
470 .position(pick_position(positions, last_5_challenge_bits, 3))
471 };
472 let [left_position, right_position] = unsafe {
474 self.table_2
475 .position(pick_position(positions, last_5_challenge_bits, 2))
476 };
477
478 let left_x = X::from(u32::from(left_position));
480 let right_x = X::from(u32::from(right_position));
481
482 let mut hasher = Sha256::new();
483 hasher.update(challenge);
484 let left_right_xs = (u64::from(left_x) << (u64::BITS as usize - usize::from(K)))
485 | (u64::from(right_x) << (u64::BITS as usize - usize::from(K * 2)));
486 hasher.update(
487 &left_right_xs.to_be_bytes()
488 [..(usize::from(K) * 2).div_ceil(u8::BITS as usize)],
489 );
490 hasher.finalize().into()
491 })
492 }
493
494 #[cfg(feature = "alloc")]
497 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
498 pub fn find_proof_raw(
499 &self,
500 first_k_challenge_bits: u32,
501 ) -> impl Iterator<Item = [u8; PROOF_SIZE::<K>]> + '_ {
502 self.table_7
504 .buckets()
505 .get(Y::bucket_range_from_first_k_bits(first_k_challenge_bits))
506 .unwrap_or(&[])
507 .iter()
508 .flat_map(move |positions| {
509 positions
510 .iter()
511 .take_while(|&&(position, _y)| position != Position::SENTINEL)
512 .filter(move |&&(_position, y)| y.first_k_bits() == first_k_challenge_bits)
513 })
514 .map(move |&(position, _y)| {
515 let table_6_proof_targets = unsafe { self.table_7.position(position) };
517
518 Self::find_proof_raw_internal(
519 &self.table_2,
520 &self.table_3,
521 &self.table_4,
522 &self.table_5,
523 &self.table_6,
524 table_6_proof_targets,
525 )
526 })
527 }
528
529 #[cfg(feature = "alloc")]
530 #[inline(always)]
531 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
532 fn find_proof_raw_internal(
533 table_2: &PrunedTable<K, 2>,
534 table_3: &PrunedTable<K, 3>,
535 table_4: &PrunedTable<K, 4>,
536 table_5: &PrunedTable<K, 5>,
537 table_6: &PrunedTable<K, 6>,
538 table_6_proof_targets: [Position; 2],
539 ) -> [u8; PROOF_SIZE::<K>] {
540 let mut proof = [0u8; _];
541
542 let positions = expand_positions(table_6_proof_targets, |position| {
548 unsafe { table_6.position(position) }
550 });
551 let positions = expand_positions(positions, |position| {
552 unsafe { table_5.position(position) }
554 });
555 let positions = expand_positions(positions, |position| {
556 unsafe { table_4.position(position) }
558 });
559 let positions = expand_positions(positions, |position| {
560 unsafe { table_3.position(position) }
562 });
563 let positions = expand_positions(positions, |position| {
564 unsafe { table_2.position(position) }
566 });
567
568 positions
569 .iter()
570 .map(|&position| {
571 X::from(u32::from(position))
573 })
574 .enumerate()
575 .for_each(|(offset, x)| {
576 let x_offset_in_bits = usize::from(K) * offset;
577 let proof_bytes = &mut proof[x_offset_in_bits / u8::BITS as usize..]
579 [..(x_offset_in_bits % u8::BITS as usize + usize::from(K))
580 .div_ceil(u8::BITS as usize)];
581
582 let x_shifted = u32::from(x)
585 << (u32::BITS as usize
586 - (usize::from(K) + x_offset_in_bits % u8::BITS as usize));
587
588 x_shifted
591 .to_be_bytes()
592 .iter()
593 .zip(proof_bytes)
594 .for_each(|(from, to)| {
595 *to |= from;
596 });
597 });
598
599 proof
600 }
601
602 #[cfg(all(feature = "alloc", any(feature = "full-chiapos", test)))]
604 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
605 pub fn find_proof(
606 &self,
607 first_challenge_bytes: [u8; 4],
608 ) -> impl Iterator<Item = [u8; PROOF_SIZE::<K>]> + '_ {
609 let first_k_challenge_bits =
610 u32::from_be_bytes(first_challenge_bytes) >> (u32::BITS as usize - usize::from(K));
611
612 self.find_proof_raw(first_k_challenge_bits)
613 }
614
615 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
618 pub fn verify_only_raw(
619 seed: &Seed,
620 first_k_challenge_bits: u32,
621 proof_of_space: &[u8; PROOF_SIZE::<K>],
622 ) -> bool {
623 let ys_and_metadata = array::from_fn::<_, 64, _>(|offset| {
624 let mut pre_x_bytes = 0u64.to_be_bytes();
625 let offset_in_bits = usize::from(K) * offset;
626 let bytes_to_copy =
627 (offset_in_bits % u8::BITS as usize + usize::from(K)).div_ceil(u8::BITS as usize);
628 pre_x_bytes[..bytes_to_copy].copy_from_slice(
630 &proof_of_space[offset_in_bits / u8::BITS as usize..][..bytes_to_copy],
631 );
632 let pre_x = u64::from_be_bytes(pre_x_bytes)
634 >> (u64::BITS as usize - (usize::from(K) + offset_in_bits % u8::BITS as usize));
635 let x = X::from(pre_x as u32 & (u32::MAX >> (u32::BITS as usize - usize::from(K))));
637
638 let y = compute_f1::<K>(x, seed);
639
640 (y, Metadata::from(x))
641 });
642
643 let mut next_ys_and_metadata = [MaybeUninit::uninit(); _];
644 let ys_and_metadata =
645 Self::collect_ys_and_metadata::<2, 1, 64>(&ys_and_metadata, &mut next_ys_and_metadata);
646 let mut next_ys_and_metadata = [MaybeUninit::uninit(); _];
647 let ys_and_metadata =
648 Self::collect_ys_and_metadata::<3, 2, 32>(ys_and_metadata, &mut next_ys_and_metadata);
649 let mut next_ys_and_metadata = [MaybeUninit::uninit(); _];
650 let ys_and_metadata =
651 Self::collect_ys_and_metadata::<4, 3, 16>(ys_and_metadata, &mut next_ys_and_metadata);
652 let mut next_ys_and_metadata = [MaybeUninit::uninit(); _];
653 let ys_and_metadata =
654 Self::collect_ys_and_metadata::<5, 4, 8>(ys_and_metadata, &mut next_ys_and_metadata);
655 let mut next_ys_and_metadata = [MaybeUninit::uninit(); _];
656 let ys_and_metadata =
657 Self::collect_ys_and_metadata::<6, 5, 4>(ys_and_metadata, &mut next_ys_and_metadata);
658 let mut next_ys_and_metadata = [MaybeUninit::uninit(); _];
659 let ys_and_metadata =
660 Self::collect_ys_and_metadata::<7, 6, 2>(ys_and_metadata, &mut next_ys_and_metadata);
661
662 let Some((y, _metadata)) = ys_and_metadata.first() else {
663 return false;
664 };
665
666 y.first_k_bits() == first_k_challenge_bits
668 }
669
670 #[cfg(any(feature = "full-chiapos", test))]
674 pub fn verify(
675 seed: &Seed,
676 challenge: &Challenge,
677 proof_of_space: &[u8; PROOF_SIZE::<K>],
678 ) -> Option<Quality> {
679 let first_k_challenge_bits =
680 u32::from_be_bytes([challenge[0], challenge[1], challenge[2], challenge[3]])
681 >> (u32::BITS as usize - usize::from(K));
682
683 if !Self::verify_only_raw(seed, first_k_challenge_bits, proof_of_space) {
684 return None;
685 }
686
687 let last_5_challenge_bits = challenge[challenge.len() - 1] & 0b0001_1111;
688
689 let mut quality_index = 0_usize.to_be_bytes();
690 quality_index[0] = last_5_challenge_bits;
691 let quality_index = usize::from_be_bytes(quality_index);
692
693 let left_right_xs_bit_offset = quality_index * usize::from(K * 2);
696 let left_right_xs_bytes = &proof_of_space[left_right_xs_bit_offset / u8::BITS as usize..]
699 [..(left_right_xs_bit_offset % u8::BITS as usize + usize::from(K * 2))
700 .div_ceil(u8::BITS as usize)];
701
702 let mut left_right_xs = 0u64.to_be_bytes();
703 left_right_xs[..left_right_xs_bytes.len()].copy_from_slice(left_right_xs_bytes);
704 let left_right_xs =
706 u64::from_be_bytes(left_right_xs) << (left_right_xs_bit_offset % u8::BITS as usize);
707 let left_right_xs_mask = u64::MAX << (u64::BITS as usize - usize::from(K * 2));
709 let left_right_xs = left_right_xs & left_right_xs_mask;
710
711 let mut hasher = Sha256::new();
712 hasher.update(challenge);
713 hasher
714 .update(&left_right_xs.to_be_bytes()[..usize::from(K * 2).div_ceil(u8::BITS as usize)]);
715 Some(hasher.finalize().into())
716 }
717
718 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
719 fn collect_ys_and_metadata<
720 'a,
721 const TABLE_NUMBER: u8,
722 const PARENT_TABLE_NUMBER: u8,
723 const N: usize,
724 >(
725 ys_and_metadata: &[(Y, Metadata<K, PARENT_TABLE_NUMBER>)],
726 next_ys_and_metadata: &'a mut [MaybeUninit<(Y, Metadata<K, TABLE_NUMBER>)>; N],
727 ) -> &'a [(Y, Metadata<K, TABLE_NUMBER>)] {
728 let mut next_offset = 0_usize;
729 for &[(left_y, left_metadata), (right_y, right_metadata)] in
730 ys_and_metadata.as_chunks::<2>().0
731 {
732 if !has_match(left_y, right_y) {
733 continue;
734 }
735
736 unsafe {
739 hint::assert_unchecked(next_offset < N);
740 }
741 next_ys_and_metadata[next_offset].write(compute_fn::<
742 K,
743 TABLE_NUMBER,
744 PARENT_TABLE_NUMBER,
745 >(
746 left_y, left_metadata, right_metadata
747 ));
748 next_offset += 1;
749 }
750
751 unsafe {
755 hint::assert_unchecked(next_offset <= N);
756 }
757
758 unsafe { next_ys_and_metadata[..next_offset].assume_init_ref() }
760 }
761}
762
763macro_rules! impl_supported {
764 ($($k: expr$(,)? )*) => {
765 $(
766impl SupportedKValue for Tables<$k> {}
767 )*
768 }
769}
770
771#[cfg(feature = "full-chiapos")]
773impl_supported!(15, 16, 18, 19, 21, 22, 23, 24, 25);
774#[cfg(any(feature = "full-chiapos", test))]
775impl_supported!(17);
776impl_supported!(20);