Skip to main content

rustfs_erasure_codec/galois_8/
policy.rs

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")]
43/// Reusable reconstruct workspace for `Option<Vec<u8>>` shard sets.
44///
45/// This workspace caches the shard-size and missing-index planning derived from a
46/// specific `Option<Vec<u8>>` shape so repeated reconstruct calls can skip the
47/// per-call plan build. It is currently intended for repeated serial classic
48/// reconstruct workloads where the missing pattern stays stable across calls.
49pub 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                        // SAFETY: each pointer/len pair comes from a present shard
256                        // and remains valid for the duration of this compute phase.
257                        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                        // SAFETY: each pointer/len pair was taken from a distinct
272                        // missing shard slot initialized to `plan.shard_len`.
273                        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                    // SAFETY: each pointer/len pair comes from a present data shard
306                    // and remains valid for the duration of this compute phase.
307                    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                    // SAFETY: each pointer/len pair was taken from a distinct
322                    // missing parity shard slot initialized to `plan.shard_len`.
323                    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    /// Prepare a reusable reconstruct workspace for an `Option<Vec<u8>>` shard layout.
564    ///
565    /// The workspace is only valid for subsequent calls that keep the same
566    /// missing-shard pattern and shard length.
567    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    /// Execute reconstruct using a previously prepared workspace.
578    ///
579    /// This skips the per-call option-vec planning pass, but the caller must
580    /// provide shards with the same missing-index layout and shard length as the
581    /// one used to prepare `workspace`.
582    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        // The workspace holds a Classic inversion-matrix plan, which does not
592        // apply to Leopard. Both Leopard families decode via the FFT reconstruct
593        // path, so route them to the serial dispatch and ignore the workspace.
594        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        // Both Leopard families decode via the FFT reconstruct path, not the
756        // Classic parallel plan below — route them to the serial dispatch.
757        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                        // SAFETY: each pointer/length pair was captured from a distinct
957                        // destination shard collected in `output_indices`. Those shards
958                        // remain alive for this scope and are only mutated through these
959                        // temporary slices.
960                        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}