use crate::layout::ElemAlignment;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VectorRequirement<O> {
Access { operand: O, elements: usize },
Elementwise { operand: O },
Masked { operand: O },
Together { operand: O, with: O },
Aligned {
operand: O,
alignment: ElemAlignment,
},
}
impl<O: Copy + Eq> VectorRequirement<O> {
pub fn operand(&self) -> O {
match self {
VectorRequirement::Access { operand, .. }
| VectorRequirement::Elementwise { operand }
| VectorRequirement::Masked { operand }
| VectorRequirement::Together { operand, .. }
| VectorRequirement::Aligned { operand, .. } => *operand,
}
}
pub fn restated<P>(self, operand: impl Fn(O) -> Option<P>) -> Option<VectorRequirement<P>> {
Some(match self {
VectorRequirement::Access {
operand: at,
elements,
} => VectorRequirement::Access {
operand: operand(at)?,
elements,
},
VectorRequirement::Elementwise { operand: at } => VectorRequirement::Elementwise {
operand: operand(at)?,
},
VectorRequirement::Masked { operand: at } => VectorRequirement::Masked {
operand: operand(at)?,
},
VectorRequirement::Together { operand: at, with } => VectorRequirement::Together {
operand: operand(at)?,
with: operand(with)?,
},
VectorRequirement::Aligned {
operand: at,
alignment,
} => VectorRequirement::Aligned {
operand: operand(at)?,
alignment,
},
})
}
pub fn admits(&self, width: usize) -> bool {
match self {
VectorRequirement::Access { elements, .. } => elements.is_multiple_of(width),
VectorRequirement::Elementwise { .. } | VectorRequirement::Masked { .. } => width == 1,
VectorRequirement::Aligned { alignment, .. } => alignment.admits(width),
VectorRequirement::Together { .. } => true,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Vectorization<O> {
requirements: Vec<VectorRequirement<O>>,
}
impl<O: Copy + Eq> Vectorization<O> {
pub fn new() -> Self {
Self {
requirements: Vec::new(),
}
}
pub fn require(&mut self, requirement: VectorRequirement<O>) {
self.requirements.push(requirement);
}
pub fn restate<S: Copy + Eq>(
&mut self,
shared: Vectorization<S>,
operand: impl Fn(S) -> Option<O>,
) {
let restated = shared
.requirements
.into_iter()
.filter_map(|requirement| requirement.restated(&operand));
self.requirements.extend(restated);
}
pub fn width(&self, operand: O, widest: impl Fn(O) -> usize) -> usize {
let mut lined = vec![operand];
let mut next = 0;
while let Some(&at) = lined.get(next) {
for requirement in &self.requirements {
let VectorRequirement::Together { operand, with } = *requirement else {
continue;
};
let other = match (operand == at, with == at) {
(true, _) => with,
(_, true) => operand,
(false, false) => continue,
};
if !lined.contains(&other) {
lined.push(other);
}
}
next += 1;
}
lined
.into_iter()
.map(|member| self.alone(member, widest(member)))
.min()
.unwrap_or(1)
}
fn alone(&self, operand: O, widest: usize) -> usize {
let admitted = |width: usize| {
self.requirements
.iter()
.filter(|requirement| requirement.operand() == operand)
.all(|requirement| requirement.admits(width))
};
let mut width = widest.max(1).next_power_of_two();
while width > 1 && (width > widest || !admitted(width)) {
width /= 2;
}
width
}
}
impl<O: Copy + Eq> Default for Vectorization<O> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn operands_lined_together_share_the_narrowest_width() {
let mut vectorization = Vectorization::new();
vectorization.require(VectorRequirement::Access {
operand: 'c',
elements: 2,
});
vectorization.require(VectorRequirement::Together {
operand: 'a',
with: 'b',
});
vectorization.require(VectorRequirement::Together {
operand: 'b',
with: 'c',
});
for operand in ['a', 'b', 'c'] {
assert_eq!(vectorization.width(operand, |_| 8), 2, "{operand}");
}
assert_eq!(vectorization.width('d', |_| 8), 8);
}
#[test]
fn the_width_reads_the_buffer_whole_lines() {
let mut vectorization = Vectorization::new();
vectorization.require(VectorRequirement::Aligned {
operand: 'a',
alignment: ElemAlignment::new(&[4, 12], &[12, 1]),
});
assert_eq!(vectorization.width('a', |_| 8), 4);
vectorization.require(VectorRequirement::Aligned {
operand: 'b',
alignment: ElemAlignment::ONE,
});
assert_eq!(vectorization.width('b', |_| 8), 1);
}
#[test]
fn a_restated_requirement_lands_on_the_bound_operand() {
let mut shared = Vectorization::new();
shared.require(VectorRequirement::Access {
operand: 'l',
elements: 4,
});
shared.require(VectorRequirement::Access {
operand: 'o',
elements: 1,
});
shared.require(VectorRequirement::Together {
operand: 'r',
with: 'o',
});
let mut family = Vectorization::new();
family.restate(shared, |operand| match operand {
'l' => Some('a'),
'r' => Some('b'),
_ => None,
});
assert_eq!(family.width('a', |_| 8), 4);
assert_eq!(family.width('b', |_| 8), 8);
}
#[test]
fn the_width_divides_every_access() {
let mut vectorization = Vectorization::new();
vectorization.require(VectorRequirement::Access {
operand: 'a',
elements: 24,
});
assert_eq!(vectorization.width('a', |_| 16), 8);
vectorization.require(VectorRequirement::Access {
operand: 'a',
elements: 12,
});
assert_eq!(vectorization.width('a', |_| 16), 4);
assert_eq!(vectorization.width('b', |_| 16), 16);
}
#[test]
fn an_elementwise_access_is_scalar() {
let mut vectorization = Vectorization::new();
vectorization.require(VectorRequirement::Access {
operand: 'a',
elements: 8,
});
vectorization.require(VectorRequirement::Elementwise { operand: 'a' });
assert_eq!(vectorization.width('a', |_| 8), 1);
}
#[test]
fn the_width_never_exceeds_the_widest_it_is_given() {
let vectorization = Vectorization::<char>::new();
assert_eq!(vectorization.width('a', |_| 2), 2);
assert_eq!(vectorization.width('a', |_| 3), 2);
assert_eq!(vectorization.width('a', |_| 1), 1);
}
}