use std::marker::PhantomData;
use cubecl::{ir::ElemType, prelude::*, std::tensor::layout::CoordsDyn};
use crate::*;
define_size!(pub(crate) LW);
#[cube]
fn register_line_words(#[comptime] words: usize) {
intrinsic!(|scope| {
scope.register_size::<LW>(words);
});
}
#[derive(CubeType, Clone)]
#[expand(derive(Clone))]
pub(crate) struct Lines<T: Numeric> {
line: Array<Vector<u32, LW>>,
window: ComptimeOption<Shared<[u32]>>,
origin: Coords<u32>,
#[cube(comptime)]
loaded: Space,
#[cube(comptime)]
strides: Vec<usize>,
#[cube(comptime)]
lines: usize,
#[cube(comptime)]
projection: Projection,
#[cube(comptime)]
field: Field,
#[cube(comptime)]
words: usize,
#[cube(comptime)]
read: UnitRead,
#[cube(comptime)]
_served: PhantomData<T>,
}
impl<T: Numeric> Lines<T> {
fn line_strides(loaded: &Space, projection: &Projection) -> (Vec<usize>, usize) {
let rank = loaded.rank();
let mut strides = vec![0; rank];
let mut lines = 1;
#[allow(clippy::needless_range_loop)]
for p in (0..rank - 1).rev() {
if projection.addresses(loaded.axis_at(p)) {
strides[p] = lines;
lines *= loaded.extent_at(p);
}
}
(strides, lines)
}
}
#[cube]
impl<T: Numeric> Lines<T> {
pub(crate) fn new(
operand: &Tile<T>,
#[comptime] level: Level,
#[comptime] read: UnitRead,
) -> Lines<T> {
let loaded = comptime!(level.child(&operand.place.space));
let rank = comptime!(loaded.rank());
let line = comptime!(loaded.extent_at(rank - 1));
let projection = operand.projection();
comptime!(assert!(
projection.addresses(loaded.axis_at(rank - 1)),
"Lines: the line runs along the operand's innermost axis, which it must address"
));
let (strides, lines) = comptime!(Lines::<T>::line_strides(&loaded, &projection));
let packing = operand.packing();
let served = elem_type_of::<T>();
let field = comptime!(match packing {
Packing::Packed { field } => field,
Packing::Plain => match served {
ElemType::Float(kind) if Field::float_bits(kind) == 32 => Field::Float(kind),
other => panic!(
"Lines: a plain operand is held as whole 32-bit words, and {other:?} is not \
one; bind the operand packed, or serve it as `f32`"
),
},
});
let per_word = comptime!(field.per_word());
comptime!(assert!(
line.is_multiple_of(per_word),
"Lines: a line of {line} values is not whole words of {per_word}"
));
let words = comptime!(line / per_word);
register_line_words(words);
let window = match comptime!(read) {
UnitRead::Shuffle => ComptimeOption::new_None(),
UnitRead::PlaneShared => {
let planes = comptime!(plane_windows(&operand.place.space, &operand.place.levels));
let cells = comptime!(lines * words);
let start = PLANE_POS.cast::<usize>() * cells;
let end = start + cells;
ComptimeOption::new_Some(
Shared::<[u32]>::new_slice(comptime!(cells * planes))
.map(|all| &all[start..end]),
)
}
};
let mut origin = Coords::<u32>::new();
#[unroll]
for _axis in 0..rank {
origin.push(0u32.runtime());
}
Lines::<T> {
line: Array::<Vector<u32, LW>>::new(1usize),
window,
origin,
loaded,
strides,
lines,
projection,
field,
words,
read,
_served: PhantomData,
}
}
pub(crate) fn lines(&self) -> comptime_type!(usize) {
comptime!(self.lines)
}
pub(crate) fn line(&self) -> comptime_type!(usize) {
let rank = comptime!(self.loaded.rank());
comptime!(self.loaded.extent_at(rank - 1))
}
pub(crate) fn projection(&self) -> comptime_type!(Projection) {
comptime!(self.projection.clone())
}
pub(crate) fn packing(&self) -> comptime_type!(Packing) {
comptime!(Packing::Packed { field: self.field })
}
pub(crate) fn load(&mut self, src: &Tile<T>) {
let rank = comptime!(self.loaded.rank());
let line = self.line();
let lines = self.lines();
let served = src.vector_size();
comptime!(assert!(
line.is_multiple_of(served),
"Lines::load: a unit reads its line of {line} values in whole reads, and the source \
serves {served} a read"
));
let reads = comptime!(line / served);
let words = comptime!(self.words);
let per_read = comptime!(words / reads);
let size!(WR) = per_read;
let packing = src.packing();
let unit = UNIT_POS_PLANE;
let mut held = Vector::<u32, LW>::empty();
if unit < comptime!(lines as u32) {
#[unroll]
for r in 0..reads {
let mut at = CoordsDyn::new();
#[unroll]
for p in 0..rank - 1 {
let stride = comptime!(self.strides[p]);
if comptime!(stride > 0) {
at.push(
unit.divided_by(comptime!(stride as u32))
.remainder(comptime!(self.loaded.extent_at(p) as u32)),
);
} else {
at.push(0u32.runtime());
}
}
at.push(comptime!(r as u32).runtime());
let got = match comptime!(packing) {
Packing::Packed { field: _ } => {
src.nd_words::<WR>(comptime!(Guard::Checked)).read(at)
}
Packing::Plain => {
let size!(SV) = served;
let values = src.nd_packed::<SV>(comptime!(Guard::Checked)).read(at);
let mut bits = Vector::<u32, WR>::empty();
#[unroll]
for j in 0..per_read {
bits.insert(j, u32::reinterpret(values.extract(j)));
}
bits
}
};
#[unroll]
for j in 0..per_read {
held.insert(comptime!(r * per_read + j), got.extract(j));
}
}
}
match comptime!(self.read) {
UnitRead::Shuffle => self.line[0usize] = held,
UnitRead::PlaneShared =>
{
#[comptime]
match &mut self.window {
ComptimeOption::Some(window) => {
if unit < comptime!(lines as u32) {
let base = (unit * comptime!(words as u32)) as usize;
#[unroll]
for j in 0..words {
window[base + j] = held.extract(j);
}
}
sync_plane();
}
ComptimeOption::None => {}
}
}
}
}
pub(crate) fn at(&self, step: &Step, #[comptime] space: Space) -> Lines<T> {
let rank = comptime!(space.rank());
let mut origin = Coords::<u32>::new();
#[unroll]
for p in 0..rank {
let axis = comptime!(space.axis_at(p));
let edge = comptime!(step.level.extent_in(&space, axis).get());
origin.push(
self.origin
.at(p)
.plus(step.coord(axis).times(edge).cast::<u32>()),
);
}
Lines::<T> {
line: self.line,
window: self.window.clone(),
origin,
loaded: comptime!(self.loaded.clone()),
strides: comptime!(self.strides.clone()),
lines: comptime!(self.lines),
projection: comptime!(self.projection.clone()),
field: comptime!(self.field),
words: comptime!(self.words),
read: comptime!(self.read),
_served: PhantomData,
}
}
#[allow(dead_code)] pub(crate) fn read(&self, coords: &Coords<u32>) -> T {
let rank = comptime!(self.loaded.rank());
let mut line = 0u32.runtime();
#[unroll]
for p in 0..rank - 1 {
let stride = comptime!(self.strides[p] as u32);
if comptime!(stride > 0) {
line = line.plus(self.origin.at(p).plus(coords.at(p)).times(stride));
}
}
let byte = self.origin.at(rank - 1).plus(coords.at(rank - 1));
let per_word = comptime!(self.field.per_word());
let words = comptime!(self.words);
let word = byte.divided_by(comptime!(per_word as u32));
let field = byte.remainder(comptime!(per_word as u32));
let held = match comptime!(self.read) {
UnitRead::Shuffle => {
let mine = self.line[0usize];
let mut got = Vector::<u32, LW>::empty();
#[unroll]
for j in 0..words {
got.insert(j, plane_shuffle(mine.extract(j), line));
}
if comptime!(words > 1) {
got.extract_dynamic(word.cast::<usize>())
} else {
got.extract(0usize)
}
}
UnitRead::PlaneShared =>
{
#[comptime]
match &self.window {
ComptimeOption::Some(window) => {
window[(line.times(comptime!(words as u32)).plus(word)) as usize]
}
ComptimeOption::None => panic!("Lines: no window was opened"),
}
}
};
let bits = comptime!(self.field.size_bits() as u32);
let only = match comptime!(per_word > 1) {
true => held >> field.times(bits),
false => held,
};
unpack_line::<T, Const<1>, Const<1>>(
Vector::<u32, Const<1>>::new(only),
comptime!(self.field),
)
.extract(0usize)
}
}