1#[cfg(feature = "std")]
2use crate::core::RuntimeParallelPolicyCache;
3
4#[cfg(feature = "std")]
5const RECONSTRUCT_DATA_MIN_PARALLEL_SHARD_BYTES: usize = 512 * 1024;
6#[cfg(feature = "std")]
7const RECONSTRUCT_FULL_MIN_PARALLEL_SHARD_BYTES: usize = 256 * 1024;
8#[cfg(feature = "std")]
9const RS_RECONSTRUCT_DATA_MIN_PARALLEL_SHARD_BYTES_ENV: &str =
10 "RS_RECONSTRUCT_DATA_MIN_PARALLEL_SHARD_BYTES";
11#[cfg(feature = "std")]
12const RS_RECONSTRUCT_FULL_MIN_PARALLEL_SHARD_BYTES_ENV: &str =
13 "RS_RECONSTRUCT_FULL_MIN_PARALLEL_SHARD_BYTES";
14#[cfg(feature = "std")]
15const RS_RECONSTRUCT_MIN_BYTES_PER_JOB_ENV: &str = "RS_RECONSTRUCT_MIN_BYTES_PER_JOB";
16#[cfg(all(feature = "std", target_arch = "aarch64"))]
17const RS_AARCH64_RECONSTRUCT_MIN_PARALLEL_SHARD_BYTES_ENV: &str =
18 "RS_AARCH64_RECONSTRUCT_MIN_PARALLEL_SHARD_BYTES";
19#[cfg(all(feature = "std", target_arch = "aarch64"))]
20const RS_AARCH64_RECONSTRUCT_MIN_BYTES_PER_JOB_ENV: &str =
21 "RS_AARCH64_RECONSTRUCT_MIN_BYTES_PER_JOB";
22#[cfg(all(feature = "std", target_arch = "aarch64"))]
23const RS_AARCH64_RECONSTRUCT_MAX_JOBS_ENV: &str = "RS_AARCH64_RECONSTRUCT_MAX_JOBS";
24#[cfg(all(feature = "std", target_arch = "aarch64"))]
25const RS_AARCH64_RECONSTRUCT_DATA_MIN_BYTES_PER_JOB_ENV: &str =
26 "RS_AARCH64_RECONSTRUCT_DATA_MIN_BYTES_PER_JOB";
27#[cfg(all(feature = "std", target_arch = "aarch64"))]
28const RS_AARCH64_RECONSTRUCT_PARITY_MIN_BYTES_PER_JOB_ENV: &str =
29 "RS_AARCH64_RECONSTRUCT_PARITY_MIN_BYTES_PER_JOB";
30
31#[cfg(feature = "std")]
32#[derive(Clone)]
33struct OptionVecReconstructPlan {
34 shard_len: usize,
35 valid_indices: smallvec::SmallVec<[usize; 32]>,
36 invalid_indices: smallvec::SmallVec<[usize; 32]>,
37 number_present: usize,
38 data_decode_matrix: Option<std::sync::Arc<crate::matrix::Matrix<super::Field>>>,
39 required_missing_data_indices: smallvec::SmallVec<[usize; 32]>,
40}
41
42#[cfg(feature = "std")]
43pub struct OptionVecReconstructWorkspace(OptionVecReconstructPlan);
50
51impl crate::ReedSolomon<super::Field> {
52 #[cfg(feature = "std")]
53 fn encode_leopard_gf8_opt<T, U>(&self, data: &[T], parity: &mut [U]) -> Result<(), crate::Error>
54 where
55 T: AsRef<[u8]> + Sync,
56 U: AsRef<[u8]> + AsMut<[u8]> + Send,
57 {
58 crate::core::leopard_gf8::encode_with_tables(
59 self.data_shard_count(),
60 self.parity_shard_count(),
61 data,
62 parity,
63 )
64 .map(|_| ())
65 }
66
67 #[cfg(feature = "std")]
68 fn decode_idx_accumulate_reduced_outputs(
69 &self,
70 matrix_rows: &[smallvec::SmallVec<[u8; 32]>],
71 inputs: &[&[u8]],
72 outputs: &mut [&mut [u8]],
73 ) {
74 debug_assert!(!outputs.is_empty());
75
76 let shard_len = inputs.first().map(|input| input.len()).unwrap_or(0);
77 if shard_len == 0 {
78 return;
79 }
80
81 let chunk_len = self.code_chunk_len(shard_len);
82 if outputs.len() == 1 {
83 let matrix_row = matrix_rows[0].as_slice();
84 outputs[0]
85 .chunks_mut(chunk_len)
86 .enumerate()
87 .for_each(|(chunk_idx, output_chunk)| {
88 let start = chunk_idx * chunk_len;
89 let end = start + output_chunk.len();
90 for i_input in 0..inputs.len() {
91 super::mul_slice_xor(
92 matrix_row[i_input],
93 &inputs[i_input][start..end],
94 output_chunk,
95 );
96 }
97 });
98 return;
99 }
100
101 if outputs.len() == 2 {
102 let (first, second) = outputs.split_at_mut(1);
103 let output0 = &mut first[0];
104 let output1 = &mut second[0];
105 let row0 = matrix_rows[0].as_slice();
106 let row1 = matrix_rows[1].as_slice();
107 output0
108 .chunks_mut(chunk_len)
109 .zip(output1.chunks_mut(chunk_len))
110 .enumerate()
111 .for_each(|(chunk_idx, (output0_chunk, output1_chunk))| {
112 let start = chunk_idx * chunk_len;
113 let end = start + output0_chunk.len();
114 let input0 = &inputs[0][start..end];
115 super::mul_slice_xor(row0[0], input0, output0_chunk);
116 super::mul_slice_xor(row1[0], input0, output1_chunk);
117 for i_input in 1..inputs.len() {
118 let input_chunk = &inputs[i_input][start..end];
119 super::mul_slice_xor(row0[i_input], input_chunk, output0_chunk);
120 super::mul_slice_xor(row1[i_input], input_chunk, output1_chunk);
121 }
122 });
123 return;
124 }
125
126 for (row, output) in matrix_rows.iter().zip(outputs.iter_mut()) {
127 output
128 .chunks_mut(chunk_len)
129 .enumerate()
130 .for_each(|(chunk_idx, output_chunk)| {
131 let start = chunk_idx * chunk_len;
132 let end = start + output_chunk.len();
133 for (coefficient, input) in row.iter().copied().zip(inputs.iter().copied()) {
134 super::mul_slice_xor(coefficient, &input[start..end], output_chunk);
135 }
136 });
137 }
138 }
139
140 #[cfg(feature = "std")]
141 fn execute_option_vec_required_data_plan(
142 &self,
143 shards: &mut [Option<Vec<u8>>],
144 plan: OptionVecReconstructPlan,
145 ) -> Result<(), crate::Error> {
146 if plan.required_missing_data_indices.is_empty() {
147 return Ok(());
148 }
149
150 let data_decode_matrix = plan
151 .data_decode_matrix
152 .as_ref()
153 .expect("non-empty plan must include decode matrix");
154 let mut matrix_rows: smallvec::SmallVec<[&[u8]; 32]> =
155 smallvec::SmallVec::with_capacity(plan.required_missing_data_indices.len());
156 for &idx in &plan.required_missing_data_indices {
157 matrix_rows.push(data_decode_matrix.get_row(idx));
158 }
159
160 let mut recovered_data: Vec<Vec<u8>> = plan
161 .required_missing_data_indices
162 .iter()
163 .map(|_| vec![0u8; plan.shard_len])
164 .collect();
165 {
166 let sub_shards_snapshot: Vec<&[u8]> = plan
167 .valid_indices
168 .iter()
169 .map(|&idx| {
170 shards[idx]
171 .as_deref()
172 .expect("valid shard index must be present")
173 })
174 .collect();
175 let sub_shards: smallvec::SmallVec<[&[u8]; 32]> =
176 sub_shards_snapshot.into_iter().collect();
177
178 let mut outputs: smallvec::SmallVec<[&mut [u8]; 32]> = recovered_data
179 .iter_mut()
180 .map(|shard| shard.as_mut_slice())
181 .collect();
182 let use_parallel = self
183 .parallel_policy(plan.shard_len, plan.required_missing_data_indices.len())
184 .use_parallel;
185 if use_parallel {
186 self.code_some_slices_par_raw(&matrix_rows, &sub_shards, &mut outputs);
187 } else {
188 self.code_some_slices_chunked(&matrix_rows, &sub_shards, &mut outputs);
189 }
190 }
191
192 for (idx, recovered) in plan
193 .required_missing_data_indices
194 .into_iter()
195 .zip(recovered_data)
196 {
197 shards[idx] = Some(recovered);
198 }
199
200 Ok(())
201 }
202
203 #[cfg(feature = "std")]
204 fn execute_option_vec_reconstruct_plan_serial(
205 &self,
206 shards: &mut [Option<Vec<u8>>],
207 plan: &OptionVecReconstructPlan,
208 data_only: bool,
209 ) -> Result<(), crate::Error> {
210 let data_decode_matrix = plan
211 .data_decode_matrix
212 .as_ref()
213 .expect("non-empty reconstruct plan must include decode matrix");
214
215 let mut missing_data_indices: smallvec::SmallVec<[usize; 32]> = smallvec::SmallVec::new();
216 let mut missing_parity_indices: smallvec::SmallVec<[usize; 32]> = smallvec::SmallVec::new();
217 for &idx in &plan.invalid_indices {
218 if idx < self.data_shard_count() {
219 missing_data_indices.push(idx);
220 } else if !data_only {
221 missing_parity_indices.push(idx);
222 }
223 }
224
225 self.record_reconstruct_runtime(
226 data_only,
227 missing_data_indices.len(),
228 missing_parity_indices.len(),
229 false,
230 );
231
232 if !missing_data_indices.is_empty() {
233 self.record_reconstruct_data_stage_runtime(plan.shard_len, missing_data_indices.len());
234
235 let mut matrix_rows: smallvec::SmallVec<[&[u8]; 32]> =
236 smallvec::SmallVec::with_capacity(missing_data_indices.len());
237 for &idx in &missing_data_indices {
238 matrix_rows.push(data_decode_matrix.get_row(idx));
239 }
240
241 {
242 let sub_shard_ptrs: smallvec::SmallVec<[(*const u8, usize); 32]> = plan
243 .valid_indices
244 .iter()
245 .map(|&idx| {
246 let shard = shards[idx]
247 .as_deref()
248 .expect("valid shard index must be present");
249 (shard.as_ptr(), shard.len())
250 })
251 .collect();
252 let sub_shards: smallvec::SmallVec<[&[u8]; 32]> = sub_shard_ptrs
253 .iter()
254 .map(|&(ptr, len)| {
255 unsafe { core::slice::from_raw_parts(ptr, len) }
258 })
259 .collect();
260 let mut output_ptrs: smallvec::SmallVec<[(*mut u8, usize); 32]> =
261 smallvec::SmallVec::with_capacity(missing_data_indices.len());
262 for &idx in &missing_data_indices {
263 let shard = shards[idx]
264 .get_or_insert_with(|| vec![0u8; plan.shard_len])
265 .as_mut_slice();
266 output_ptrs.push((shard.as_mut_ptr(), shard.len()));
267 }
268 let mut outputs: smallvec::SmallVec<[&mut [u8]; 32]> = output_ptrs
269 .iter()
270 .map(|&(ptr, len)| {
271 unsafe { core::slice::from_raw_parts_mut(ptr, len) }
274 })
275 .collect();
276 self.code_some_slices_chunked(&matrix_rows, &sub_shards, &mut outputs);
277 }
278 }
279
280 if data_only || missing_parity_indices.is_empty() {
281 return Ok(());
282 }
283
284 self.record_reconstruct_parity_stage_runtime(plan.shard_len, missing_parity_indices.len());
285
286 let parity_rows = self.get_parity_rows();
287 let mut matrix_rows: smallvec::SmallVec<[&[u8]; 32]> =
288 smallvec::SmallVec::with_capacity(missing_parity_indices.len());
289 for &idx in &missing_parity_indices {
290 matrix_rows.push(parity_rows[idx - self.data_shard_count()]);
291 }
292
293 {
294 let all_data_ptrs: smallvec::SmallVec<[(*const u8, usize); 32]> = shards
295 .iter()
296 .take(self.data_shard_count())
297 .map(|shard| {
298 let data = shard.as_deref().expect("data shard must be present");
299 (data.as_ptr(), data.len())
300 })
301 .collect();
302 let all_data: smallvec::SmallVec<[&[u8]; 32]> = all_data_ptrs
303 .iter()
304 .map(|&(ptr, len)| {
305 unsafe { core::slice::from_raw_parts(ptr, len) }
308 })
309 .collect();
310 let mut output_ptrs: smallvec::SmallVec<[(*mut u8, usize); 32]> =
311 smallvec::SmallVec::with_capacity(missing_parity_indices.len());
312 for &idx in &missing_parity_indices {
313 let shard = shards[idx]
314 .get_or_insert_with(|| vec![0u8; plan.shard_len])
315 .as_mut_slice();
316 output_ptrs.push((shard.as_mut_ptr(), shard.len()));
317 }
318 let mut outputs: smallvec::SmallVec<[&mut [u8]; 32]> = output_ptrs
319 .iter()
320 .map(|&(ptr, len)| {
321 unsafe { core::slice::from_raw_parts_mut(ptr, len) }
324 })
325 .collect();
326 self.code_some_slices_chunked(&matrix_rows, &all_data, &mut outputs);
327 }
328
329 Ok(())
330 }
331
332 #[cfg(feature = "std")]
333 fn plan_option_vec_reconstruct(
334 &self,
335 shards: &[Option<Vec<u8>>],
336 required: Option<&[bool]>,
337 ) -> Result<OptionVecReconstructPlan, crate::Error> {
338 let mut number_present = 0;
339 let mut shard_len = None;
340 for shard in shards.iter() {
341 if let Some(shard) = shard.as_ref() {
342 if shard.is_empty() {
343 return Err(crate::Error::EmptyShard);
344 }
345 number_present += 1;
346 if let Some(old_len) = shard_len
347 && shard.len() != old_len
348 {
349 return Err(crate::Error::IncorrectShardSize);
350 }
351 shard_len = Some(shard.len());
352 }
353 }
354
355 if number_present == self.total_shard_count() {
356 return Ok(OptionVecReconstructPlan {
357 shard_len: 0,
358 valid_indices: smallvec::SmallVec::new(),
359 invalid_indices: smallvec::SmallVec::new(),
360 number_present,
361 data_decode_matrix: None,
362 required_missing_data_indices: smallvec::SmallVec::new(),
363 });
364 }
365 if number_present < self.data_shard_count() {
366 return Err(crate::Error::TooFewShardsPresent);
367 }
368
369 let shard_len = shard_len.expect("at least one shard present; qed");
370 let mut valid_indices =
371 smallvec::SmallVec::<[usize; 32]>::with_capacity(self.data_shard_count());
372 let mut invalid_indices =
373 smallvec::SmallVec::<[usize; 32]>::with_capacity(self.total_shard_count());
374 for (idx, shard) in shards.iter().enumerate() {
375 if shard.is_some() {
376 if valid_indices.len() < self.data_shard_count() {
377 valid_indices.push(idx);
378 }
379 } else {
380 invalid_indices.push(idx);
381 }
382 }
383
384 let data_decode_matrix = self.get_data_decode_matrix(&valid_indices, &invalid_indices)?;
385 let required_missing_data_indices = required
386 .map(|required| {
387 (0..self.data_shard_count())
388 .filter(|&i| required[i] && shards[i].is_none())
389 .collect()
390 })
391 .unwrap_or_default();
392
393 Ok(OptionVecReconstructPlan {
394 shard_len,
395 valid_indices,
396 invalid_indices,
397 number_present,
398 data_decode_matrix: Some(data_decode_matrix),
399 required_missing_data_indices,
400 })
401 }
402
403 #[cfg(feature = "std")]
404 fn first_shard_len<T: AsRef<[u8]>>(slices: &[T]) -> usize {
405 slices
406 .first()
407 .map(|shard| shard.as_ref().len())
408 .unwrap_or(0)
409 }
410
411 #[cfg(feature = "std")]
412 fn first_present_shard_len(shards: &[Option<Vec<u8>>]) -> usize {
413 shards
414 .iter()
415 .find_map(|shard| shard.as_ref().map(|shard| shard.len()))
416 .unwrap_or(0)
417 }
418
419 #[cfg(feature = "std")]
420 fn summarize_option_vec_reconstruct_shape(
421 &self,
422 shards: &[Option<Vec<u8>>],
423 ) -> Result<(usize, usize, usize), crate::Error> {
424 let mut number_present = 0usize;
425 let mut shard_len = None;
426 let mut missing_total = 0usize;
427 let mut missing_data = 0usize;
428
429 for (idx, shard) in shards.iter().enumerate() {
430 if let Some(shard) = shard.as_ref() {
431 if shard.is_empty() {
432 return Err(crate::Error::EmptyShard);
433 }
434 number_present += 1;
435 if let Some(old_len) = shard_len
436 && shard.len() != old_len
437 {
438 return Err(crate::Error::IncorrectShardSize);
439 }
440 shard_len = Some(shard.len());
441 } else {
442 missing_total += 1;
443 if idx < self.data_shard_count() {
444 missing_data += 1;
445 }
446 }
447 }
448
449 if missing_total == 0 {
450 return Ok((0, 0, 0));
451 }
452 if number_present < self.data_shard_count() {
453 return Err(crate::Error::TooFewShardsPresent);
454 }
455
456 Ok((shard_len.unwrap_or(0), missing_data, missing_total))
457 }
458
459 #[cfg(feature = "std")]
460 fn should_parallel_data_path(&self, shard_len: usize, output_shards: usize) -> bool {
461 self.parallel_policy(shard_len, output_shards).use_parallel
462 }
463
464 #[cfg(feature = "std")]
465 pub(crate) fn reconstruct_parallel_decision_with(
466 &self,
467 shard_len: usize,
468 missing_data: usize,
469 missing_total: usize,
470 data_only: bool,
471 available_parallelism: usize,
472 ) -> crate::ParallelDecision {
473 let output_shards = if data_only {
474 missing_data
475 } else {
476 missing_total
477 };
478 let tuned = self.policy_cache.reconstruct_policy(data_only);
479 tuned.decide(
480 shard_len,
481 self.data_shard_count(),
482 output_shards,
483 available_parallelism,
484 )
485 }
486
487 #[cfg(feature = "std")]
488 fn reconstruct_parallel_decision(
489 &self,
490 shard_len: usize,
491 missing_data: usize,
492 missing_total: usize,
493 data_only: bool,
494 ) -> crate::ParallelDecision {
495 self.reconstruct_parallel_decision_with(
496 shard_len,
497 missing_data,
498 missing_total,
499 data_only,
500 self.policy_cache.available_parallelism,
501 )
502 }
503
504 #[cfg(feature = "std")]
505 fn reconstruct_stage_policies(
506 &self,
507 data_only: bool,
508 ) -> (crate::ParallelPolicy, crate::ParallelPolicy) {
509 self.policy_cache.reconstruct_stage_policies(data_only)
510 }
511
512 #[cfg(feature = "std")]
513 #[doc(hidden)]
514 pub fn reconstruct_stage_policies_for_bench(
515 &self,
516 data_only: bool,
517 ) -> (crate::ParallelPolicy, crate::ParallelPolicy) {
518 self.reconstruct_stage_policies(data_only)
519 }
520
521 #[cfg(feature = "std")]
522 #[doc(hidden)]
523 pub fn reconstruct_execution_context_for_bench(
524 &self,
525 shard_len: usize,
526 missing_data: usize,
527 missing_total: usize,
528 data_only: bool,
529 available_parallelism: usize,
530 ) -> (
531 crate::ParallelDecision,
532 crate::ParallelPolicy,
533 crate::ParallelPolicy,
534 ) {
535 let decision = self.reconstruct_parallel_decision_with(
536 shard_len,
537 missing_data,
538 missing_total,
539 data_only,
540 available_parallelism,
541 );
542 let (data_policy, parity_policy) = self.reconstruct_stage_policies(data_only);
543 (decision, data_policy, parity_policy)
544 }
545
546 #[cfg(feature = "std")]
547 #[doc(hidden)]
548 pub fn plan_option_vec_reconstruct_for_bench(
549 &self,
550 shards: &[Option<Vec<u8>>],
551 required: Option<&[bool]>,
552 ) -> Result<(usize, usize, usize, usize), crate::Error> {
553 let plan = self.plan_option_vec_reconstruct(shards, required)?;
554 Ok((
555 plan.shard_len,
556 plan.number_present,
557 plan.valid_indices.len(),
558 plan.invalid_indices.len(),
559 ))
560 }
561
562 #[cfg(feature = "std")]
563 pub fn prepare_reconstruct_opt_workspace(
568 &self,
569 shards: &[Option<Vec<u8>>],
570 ) -> Result<OptionVecReconstructWorkspace, crate::Error> {
571 Ok(OptionVecReconstructWorkspace(
572 self.plan_option_vec_reconstruct(shards, None)?,
573 ))
574 }
575
576 #[cfg(feature = "std")]
577 pub fn reconstruct_opt_with_workspace(
583 &self,
584 shards: &mut [Option<Vec<u8>>],
585 workspace: &OptionVecReconstructWorkspace,
586 ) -> Result<(), crate::Error> {
587 if workspace.0.shard_len == 0 {
588 return Ok(());
589 }
590
591 if self.is_leopard_family() {
595 return self.reconstruct(shards);
596 }
597
598 let mut invalid_indices = smallvec::SmallVec::<[usize; 32]>::new();
599 let mut shard_len = None;
600 let mut number_present = 0usize;
601 for (idx, shard) in shards.iter().enumerate() {
602 if let Some(shard) = shard.as_ref() {
603 if shard.is_empty() {
604 return Err(crate::Error::EmptyShard);
605 }
606 number_present += 1;
607 if let Some(old_len) = shard_len
608 && shard.len() != old_len
609 {
610 return Err(crate::Error::IncorrectShardSize);
611 }
612 shard_len = Some(shard.len());
613 } else {
614 invalid_indices.push(idx);
615 }
616 }
617
618 if invalid_indices != workspace.0.invalid_indices {
619 return Err(crate::Error::InvalidShardFlags);
620 }
621 if number_present != workspace.0.number_present {
622 return Err(crate::Error::TooFewShardsPresent);
623 }
624 if shard_len.unwrap_or(0) != workspace.0.shard_len {
625 return Err(crate::Error::IncorrectShardSize);
626 }
627
628 self.execute_option_vec_reconstruct_plan_serial(shards, &workspace.0, false)
629 }
630
631 #[cfg(feature = "std")]
632 #[doc(hidden)]
633 pub fn execute_option_vec_reconstruct_plan_serial_for_bench(
634 &self,
635 shards: &mut [Option<Vec<u8>>],
636 data_only: bool,
637 ) -> Result<(), crate::Error> {
638 let plan = self.plan_option_vec_reconstruct(shards, None)?;
639 self.execute_option_vec_reconstruct_plan_serial(shards, &plan, data_only)
640 }
641
642 #[cfg(feature = "std")]
643 pub fn encode_opt<T, U>(&self, mut shards: T) -> Result<(), crate::Error>
644 where
645 T: AsRef<[U]> + AsMut<[U]>,
646 U: AsRef<[u8]> + AsMut<[u8]> + Send + Sync,
647 {
648 if self.is_leopard_gf8_family() {
649 let slices = shards.as_mut();
650 if slices.len() != self.total_shard_count() {
651 return Err(crate::Error::TooFewShards);
652 }
653 if slices.is_empty() {
654 return Err(crate::Error::TooFewShards);
655 }
656 let shard_len = slices[0].as_ref().len();
657 if shard_len == 0 {
658 return Err(crate::Error::EmptyShard);
659 }
660 for shard in slices.iter() {
661 if shard.as_ref().len() != shard_len {
662 return Err(crate::Error::IncorrectShardSize);
663 }
664 }
665 let (data, parity) = slices.split_at_mut(self.data_shard_count());
666 return self.encode_leopard_gf8_opt(&*data, parity);
667 }
668
669 let shard_len = Self::first_shard_len(shards.as_ref());
670 if self.should_parallel_data_path(shard_len, self.parity_shard_count()) {
671 self.encode_par(shards)
672 } else {
673 self.encode(shards)
674 }
675 }
676
677 #[cfg(feature = "std")]
678 pub fn encode_sep_opt<T, U>(&self, data: &[T], parity: &mut [U]) -> Result<(), crate::Error>
679 where
680 T: AsRef<[u8]> + Sync,
681 U: AsRef<[u8]> + AsMut<[u8]> + Send,
682 {
683 if self.is_leopard_gf8_family() {
684 return self.encode_leopard_gf8_opt(data, parity);
685 }
686
687 let shard_len = Self::first_shard_len(data);
688 if self.should_parallel_data_path(shard_len, parity.len()) {
689 self.encode_sep_par(data, parity)
690 } else {
691 self.encode_sep(data, parity)
692 }
693 }
694
695 #[cfg(feature = "std")]
696 pub fn verify_opt<T>(&self, slices: &[T]) -> Result<bool, crate::Error>
697 where
698 T: AsRef<[u8]> + Sync,
699 {
700 self.ensure_classic_family_execution()?;
701 if self.is_leopard_gf8_family() {
702 return self.verify(slices);
703 }
704 let shard_len = Self::first_shard_len(slices);
705 if self.should_parallel_data_path(shard_len, self.parity_shard_count()) {
706 self.verify_par(slices)
707 } else {
708 self.verify(slices)
709 }
710 }
711
712 #[cfg(feature = "std")]
713 pub fn verify_with_buffer_opt<T, U>(
714 &self,
715 slices: &[T],
716 buffer: &mut [U],
717 ) -> Result<bool, crate::Error>
718 where
719 T: AsRef<[u8]> + Sync,
720 U: AsRef<[u8]> + AsMut<[u8]> + Send,
721 {
722 self.ensure_classic_family_execution()?;
723 if self.is_leopard_gf8_family() {
724 return self.verify_with_buffer(slices, buffer);
725 }
726 let shard_len = Self::first_shard_len(slices);
727 if self.should_parallel_data_path(shard_len, buffer.len()) {
728 self.verify_with_buffer_par(slices, buffer)
729 } else {
730 self.verify_with_buffer(slices, buffer)
731 }
732 }
733
734 #[cfg(feature = "std")]
735 pub fn verify_with_workspace_opt(
736 &self,
737 slices: &[Vec<u8>],
738 workspace: &mut crate::VerifyWorkspace<crate::galois_8::Field>,
739 ) -> Result<bool, crate::Error> {
740 self.ensure_classic_family_execution()?;
741 if self.is_leopard_gf8_family() {
742 return self.verify_with_workspace(slices, workspace);
743 }
744 let shard_len = Self::first_shard_len(slices);
745 workspace.resize(self, shard_len);
746 if self.should_parallel_data_path(shard_len, self.parity_shard_count()) {
747 self.verify_with_buffer_par(slices, workspace.as_mut_shards())
748 } else {
749 self.verify_with_buffer(slices, workspace.as_mut_shards())
750 }
751 }
752
753 #[cfg(feature = "std")]
754 pub fn reconstruct_opt(&self, shards: &mut [Option<Vec<u8>>]) -> Result<(), crate::Error> {
755 if self.is_leopard_family() {
758 return self.reconstruct(shards);
759 }
760 let (shard_len, missing_data, missing) =
761 self.summarize_option_vec_reconstruct_shape(shards)?;
762 if missing == 0 {
763 return Ok(());
764 }
765 let decision = self.reconstruct_parallel_decision(shard_len, missing_data, missing, false);
766 self.record_reconstruct_entry_path(decision.use_parallel);
767 if decision.use_parallel {
768 let (data_policy, parity_policy) = self.reconstruct_stage_policies(false);
769 self.reconstruct_internal_option_vec_par_with_stage_policies(
770 shards,
771 false,
772 data_policy,
773 parity_policy,
774 )
775 } else {
776 self.record_reconstruct_opt_fallback_serial_path();
777 self.reconstruct(shards)
778 }
779 }
780
781 #[cfg(feature = "std")]
782 pub fn reconstruct_data_opt(&self, shards: &mut [Option<Vec<u8>>]) -> Result<(), crate::Error> {
783 if self.is_leopard_family() {
784 return self.reconstruct_data(shards);
785 }
786 let (shard_len, missing_data, missing) =
787 self.summarize_option_vec_reconstruct_shape(shards)?;
788 if missing == 0 {
789 return Ok(());
790 }
791 let decision = self.reconstruct_parallel_decision(shard_len, missing_data, missing, true);
792 self.record_reconstruct_entry_path(decision.use_parallel);
793 if decision.use_parallel {
794 let (data_policy, _parity_policy) = self.reconstruct_stage_policies(true);
795 self.reconstruct_internal_option_vec_par_with_policy(shards, true, data_policy)
796 } else {
797 self.reconstruct_data(shards)
798 }
799 }
800
801 #[cfg(feature = "std")]
802 pub fn reconstruct_some_opt(
803 &self,
804 shards: &mut [Option<Vec<u8>>],
805 required: &[bool],
806 ) -> Result<(), crate::Error> {
807 if self.is_leopard_family() {
808 return self.reconstruct_some(shards, required);
809 }
810 if required.len() != self.total_shard_count() {
811 return Err(crate::Error::InvalidShardFlags);
812 }
813
814 let data_only = required
815 .iter()
816 .enumerate()
817 .all(|(idx, required)| !*required || idx < self.data_shard_count());
818
819 if data_only {
820 let plan = self.plan_option_vec_reconstruct(shards, Some(required))?;
821 if plan.number_present == self.total_shard_count() {
822 return Ok(());
823 }
824 return self.execute_option_vec_required_data_plan(shards, plan);
825 }
826
827 self.reconstruct_opt(shards)?;
828 Ok(())
829 }
830
831 #[cfg(feature = "std")]
832 #[allow(clippy::needless_range_loop)]
833 pub fn decode_idx(
834 &self,
835 dst: &mut [Option<Vec<u8>>],
836 expect_input: Option<&[bool]>,
837 input: &[Option<Vec<u8>>],
838 ) -> Result<(), crate::Error> {
839 if self.is_leopard_gf8_family() {
840 return Err(crate::Error::UnsupportedCodecFamily);
841 }
842 self.ensure_classic_family_execution()?;
843 if dst.len() != self.total_shard_count() || input.len() != self.total_shard_count() {
844 return Err(crate::Error::TooFewShards);
845 }
846
847 if let Some(expect_input) = expect_input {
848 if expect_input.len() != self.total_shard_count() {
849 return Err(crate::Error::InvalidShardFlags);
850 }
851
852 let mut valid_indices =
853 smallvec::SmallVec::<[usize; 32]>::with_capacity(self.data_shard_count());
854 let mut invalid_indices =
855 smallvec::SmallVec::<[usize; 32]>::with_capacity(self.total_shard_count());
856
857 for (idx, expected) in expect_input.iter().copied().enumerate() {
858 if expected {
859 valid_indices.push(idx);
860 } else {
861 invalid_indices.push(idx);
862 }
863 }
864
865 if valid_indices.len() < self.data_shard_count() {
866 return Err(crate::Error::TooFewShardsPresent);
867 }
868
869 let shard_len = input
870 .iter()
871 .chain(dst.iter())
872 .find_map(|shard| shard.as_ref().map(|shard| shard.len()))
873 .ok_or(crate::Error::TooFewShardsPresent)?;
874
875 for shard in input.iter().flatten() {
876 if shard.len() != shard_len {
877 return Err(crate::Error::IncorrectShardSize);
878 }
879 }
880 for shard in dst.iter().flatten() {
881 if shard.len() != shard_len {
882 return Err(crate::Error::IncorrectShardSize);
883 }
884 }
885
886 let mut output_indices: smallvec::SmallVec<[usize; 32]> = smallvec::SmallVec::new();
887
888 for (idx, shard) in dst.iter().enumerate() {
889 let Some(_dst_shard) = shard.as_ref() else {
890 continue;
891 };
892 output_indices.push(idx);
893 }
894
895 if output_indices.is_empty() {
896 return Ok(());
897 }
898
899 let mut input_positions = smallvec::SmallVec::<[usize; 32]>::new();
900 let mut input_refs = smallvec::SmallVec::<[&[u8]; 32]>::new();
901 for (col, &idx) in valid_indices
902 .iter()
903 .take(self.data_shard_count())
904 .enumerate()
905 {
906 if let Some(shard) = input[idx].as_deref() {
907 input_positions.push(col);
908 input_refs.push(shard);
909 }
910 }
911
912 if input_refs.is_empty() {
913 return Ok(());
914 }
915
916 let data_decode_matrix =
917 self.get_data_decode_matrix(&valid_indices, &invalid_indices)?;
918 let parity_rows = self.get_parity_rows();
919 let mut reduced_rows: smallvec::SmallVec<[smallvec::SmallVec<[u8; 32]>; 32]> =
920 smallvec::SmallVec::with_capacity(output_indices.len());
921 for &idx in &output_indices {
922 let mut row = smallvec::SmallVec::<[u8; 32]>::with_capacity(input_positions.len());
923 if idx < self.data_shard_count() {
924 for &col in &input_positions {
925 row.push(data_decode_matrix.get(idx, col));
926 }
927 } else {
928 let parity_row = parity_rows[idx - self.data_shard_count()];
929 for &col in &input_positions {
930 let mut acc = 0u8;
931 for i in 0..self.data_shard_count() {
932 acc ^= super::mul(parity_row[i], data_decode_matrix.get(i, col));
933 }
934 row.push(acc);
935 }
936 }
937 reduced_rows.push(row);
938 }
939
940 let mut output_ptrs: smallvec::SmallVec<[(*mut u8, usize); 32]> =
941 smallvec::SmallVec::with_capacity(output_indices.len());
942 for &idx in &output_indices {
943 let dst_shard = dst[idx]
944 .as_deref_mut()
945 .expect("output index was collected only for present destinations");
946 if dst_shard.len() != shard_len {
947 return Err(crate::Error::IncorrectShardSize);
948 }
949 output_ptrs.push((dst_shard.as_mut_ptr(), dst_shard.len()));
950 }
951
952 {
953 let mut output_refs: smallvec::SmallVec<[&mut [u8]; 32]> = output_ptrs
954 .iter()
955 .map(|&(ptr, len)| {
956 unsafe { core::slice::from_raw_parts_mut(ptr, len) }
961 })
962 .collect();
963
964 self.decode_idx_accumulate_reduced_outputs(
965 &reduced_rows,
966 &input_refs,
967 &mut output_refs,
968 );
969 }
970
971 return Ok(());
972 }
973
974 for (dst_shard, input_shard) in dst.iter_mut().zip(input.iter()) {
975 match (dst_shard.as_deref_mut(), input_shard.as_deref()) {
976 (Some(dst), Some(input)) => {
977 if dst.len() != input.len() {
978 return Err(crate::Error::IncorrectShardSize);
979 }
980 for (dst_byte, input_byte) in dst.iter_mut().zip(input.iter()) {
981 *dst_byte ^= *input_byte;
982 }
983 }
984 (None, Some(_)) => return Err(crate::Error::TooFewShards),
985 _ => {}
986 }
987 }
988
989 Ok(())
990 }
991}
992
993#[cfg(feature = "std")]
994fn reconstruct_parallel_policy_default(
995 base: crate::ParallelPolicy,
996 data_only: bool,
997) -> crate::ParallelPolicy {
998 let data_only_min = parse_positive_env_usize(RS_RECONSTRUCT_DATA_MIN_PARALLEL_SHARD_BYTES_ENV)
999 .unwrap_or(RECONSTRUCT_DATA_MIN_PARALLEL_SHARD_BYTES);
1000 let full_min = parse_positive_env_usize(RS_RECONSTRUCT_FULL_MIN_PARALLEL_SHARD_BYTES_ENV)
1001 .unwrap_or(RECONSTRUCT_FULL_MIN_PARALLEL_SHARD_BYTES);
1002 let min_bytes_per_job = parse_positive_env_usize(RS_RECONSTRUCT_MIN_BYTES_PER_JOB_ENV)
1003 .unwrap_or(base.min_bytes_per_job);
1004 if data_only {
1005 crate::ParallelPolicy {
1006 min_parallel_shard_bytes: core::cmp::max(base.min_parallel_shard_bytes, data_only_min),
1007 min_bytes_per_job,
1008 max_jobs: base.max_jobs,
1009 l2_cache_bytes: base.l2_cache_bytes,
1010 }
1011 } else {
1012 crate::ParallelPolicy {
1013 min_parallel_shard_bytes: core::cmp::max(base.min_parallel_shard_bytes / 2, full_min),
1014 min_bytes_per_job,
1015 max_jobs: base.max_jobs,
1016 l2_cache_bytes: base.l2_cache_bytes,
1017 }
1018 }
1019}
1020
1021#[cfg(feature = "std")]
1022fn parse_positive_env_usize(name: &str) -> Option<usize> {
1023 std::env::var(name)
1024 .ok()
1025 .and_then(|value| value.parse::<usize>().ok())
1026 .filter(|value| *value > 0)
1027}
1028
1029#[cfg(all(feature = "std", target_arch = "aarch64"))]
1030fn reconstruct_policy_cache_aarch64(base: crate::ParallelPolicy) -> RuntimeParallelPolicyCache {
1031 let mut reconstruct_full_data = reconstruct_parallel_policy_default(base, false);
1032 if let Some(value) =
1033 parse_positive_env_usize(RS_AARCH64_RECONSTRUCT_MIN_PARALLEL_SHARD_BYTES_ENV)
1034 {
1035 reconstruct_full_data.min_parallel_shard_bytes = value;
1036 }
1037 if let Some(value) = parse_positive_env_usize(RS_AARCH64_RECONSTRUCT_MIN_BYTES_PER_JOB_ENV) {
1038 reconstruct_full_data.min_bytes_per_job = value;
1039 }
1040 if let Some(value) = std::env::var(RS_AARCH64_RECONSTRUCT_MAX_JOBS_ENV)
1041 .ok()
1042 .and_then(|value| value.parse::<usize>().ok())
1043 {
1044 reconstruct_full_data.max_jobs = value;
1045 }
1046
1047 let mut reconstruct_data = reconstruct_parallel_policy_default(base, true);
1048 reconstruct_data.min_parallel_shard_bytes = reconstruct_full_data.min_parallel_shard_bytes;
1049 reconstruct_data.min_bytes_per_job = reconstruct_full_data.min_bytes_per_job;
1050 reconstruct_data.max_jobs = reconstruct_full_data.max_jobs;
1051
1052 let mut reconstruct_full_parity = reconstruct_parallel_policy_default(base, false);
1053 if let Some(value) =
1054 parse_positive_env_usize(RS_AARCH64_RECONSTRUCT_PARITY_MIN_BYTES_PER_JOB_ENV)
1055 {
1056 reconstruct_full_parity.min_bytes_per_job = value;
1057 }
1058
1059 if let Some(value) = parse_positive_env_usize(RS_AARCH64_RECONSTRUCT_DATA_MIN_BYTES_PER_JOB_ENV)
1060 {
1061 reconstruct_data.min_bytes_per_job = value;
1062 }
1063
1064 RuntimeParallelPolicyCache {
1065 available_parallelism: std::thread::available_parallelism()
1066 .map(|parallelism| parallelism.get())
1067 .unwrap_or(1),
1068 data: base,
1069 reconstruct_data,
1070 reconstruct_full_data,
1071 reconstruct_full_parity,
1072 }
1073}
1074
1075#[cfg(all(feature = "std", not(target_arch = "aarch64")))]
1076pub(crate) fn resolve_runtime_parallel_policy_cache(
1077 base: crate::ParallelPolicy,
1078) -> RuntimeParallelPolicyCache {
1079 let reconstruct_data = reconstruct_parallel_policy_default(base, true);
1080 let reconstruct_full = reconstruct_parallel_policy_default(base, false);
1081 RuntimeParallelPolicyCache {
1082 available_parallelism: std::thread::available_parallelism()
1083 .map(|parallelism| parallelism.get())
1084 .unwrap_or(1),
1085 data: base,
1086 reconstruct_data,
1087 reconstruct_full_data: reconstruct_full,
1088 reconstruct_full_parity: reconstruct_full,
1089 }
1090}
1091
1092#[cfg(all(feature = "std", target_arch = "aarch64"))]
1093pub(crate) fn resolve_runtime_parallel_policy_cache(
1094 base: crate::ParallelPolicy,
1095) -> RuntimeParallelPolicyCache {
1096 reconstruct_policy_cache_aarch64(base)
1097}