use crate::{StackError, UnitStack, ValueWidthPolicy};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ShuffleError {
ZeroWidthGroup {
group: usize,
},
UnknownUnit {
unit: usize,
},
SplitGroup {
group: usize,
},
LayoutMismatch {
group: usize,
expected: usize,
actual: usize,
},
Stack(StackError),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ShufflePlan {
input_widths: Vec<usize>,
output_groups: Vec<usize>,
}
impl ShufflePlan {
pub fn new(
input_widths: impl IntoIterator<Item = usize>,
output_units: impl IntoIterator<Item = usize>,
) -> Result<Self, ShuffleError> {
let input_widths: Vec<_> = input_widths.into_iter().collect();
let mut unit_to_group = Vec::new();
let mut starts = Vec::with_capacity(input_widths.len());
for (group, &width) in input_widths.iter().enumerate() {
if width == 0 {
return Err(ShuffleError::ZeroWidthGroup { group });
}
starts.push(unit_to_group.len());
unit_to_group.extend(std::iter::repeat_n(group, width));
}
let output_units: Vec<_> = output_units.into_iter().collect();
let mut output_groups = Vec::new();
let mut cursor = 0;
while cursor < output_units.len() {
let unit = output_units[cursor];
let Some(&group) = unit_to_group.get(unit) else {
return Err(ShuffleError::UnknownUnit { unit });
};
let start = starts[group];
let width = input_widths[group];
let end = cursor.saturating_add(width);
let whole_group = output_units
.get(cursor..end)
.is_some_and(|units| units.iter().copied().eq(start..start + width));
if !whole_group {
return Err(ShuffleError::SplitGroup { group });
}
output_groups.push(group);
cursor = end;
}
Ok(Self {
input_widths,
output_groups,
})
}
pub fn execute<P>(&self, stack: &mut UnitStack<P>) -> Result<(), ShuffleError>
where
P: ValueWidthPolicy,
P::Value: Clone,
{
let common = self.input_widths.len().min(stack.values.len());
for group in 0..common {
let actual = P::width(&stack.values[group]);
let expected = self.input_widths[group];
if actual != expected {
return Err(ShuffleError::LayoutMismatch {
group,
expected,
actual,
});
}
}
if self.input_widths.len() != stack.values.len() {
let group = common;
return Err(ShuffleError::LayoutMismatch {
group,
expected: self.input_widths.get(group).copied().unwrap_or(0),
actual: stack.values.get(group).map(P::width).unwrap_or(0),
});
}
let mut staged = UnitStack::<P>::new(stack.limit);
for &group in &self.output_groups {
staged
.push(stack.values[group].clone())
.map_err(ShuffleError::Stack)?;
}
*stack = staged;
Ok(())
}
}