use cubecl::prelude::*;
use super::{ComputeScope, CubeOrder, Level, cube_positions};
use crate::{Axis, Coords, CubeAxis, Integer, IntegerExpand, Space};
const CUBES_PER_GRID_DIMENSION: usize = 65_535;
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct PlaneOrder {
block: Vec<(Axis, usize)>,
block_order: CubeOrder,
planes_per_cube: usize,
}
impl PlaneOrder {
pub fn new(block: &[(Axis, usize)], block_order: CubeOrder) -> Self {
for &(axis, tiles) in block {
assert!(
tiles > 0,
"PlaneOrder: a block holds no tile along {axis:?}"
);
}
let block = block.to_vec();
let planes_per_cube = block.iter().map(|&(_, tiles)| tiles).product();
Self {
block,
block_order: block_order.canonicalize(),
planes_per_cube,
}
}
pub fn cubes_of(self, planes: usize) -> Self {
assert!(
planes > 0,
"PlaneOrder::cubes_of: a cube holds at least one plane"
);
Self {
planes_per_cube: planes,
..self
}
}
pub fn planes_per_cube(&self) -> usize {
self.planes_per_cube
}
pub fn block_along(&self, axis: Axis) -> usize {
self.block
.iter()
.find(|&&(a, _)| a == axis)
.map_or(1, |&(_, tiles)| tiles)
}
pub fn block_order(&self) -> CubeOrder {
self.block_order
}
pub(crate) fn block_axes(&self) -> impl Iterator<Item = Axis> + '_ {
self.block.iter().map(|&(axis, _)| axis)
}
pub(crate) fn block_planes(&self) -> usize {
self.block.iter().map(|&(_, tiles)| tiles).product()
}
pub(crate) fn one_block_a_cube(&self) -> bool {
self.block_planes() == self.planes_per_cube
}
pub(crate) fn grid(&self, level: &Level, space: &Space) -> (usize, usize) {
let planes = self.blocks(level, space) * self.block_planes();
let cubes = planes.div_ceil(self.planes_per_cube).max(1);
if cubes <= CUBES_PER_GRID_DIMENSION {
return (cubes, 1);
}
let rows = cubes.div_ceil(CUBES_PER_GRID_DIMENSION);
let x = (rows..=CUBES_PER_GRID_DIMENSION)
.find(|&rows| cubes.is_multiple_of(rows) && cubes / rows <= CUBES_PER_GRID_DIMENSION)
.map_or(CUBES_PER_GRID_DIMENSION, |rows| cubes / rows);
(x, cubes.div_ceil(x))
}
pub(crate) fn overruns(&self, level: &Level, space: &Space, axis: Axis) -> bool {
let ragged = !level
.tiles(space, axis)
.is_multiple_of(self.block_along(axis));
if ragged {
return true;
}
if Self::spare_blocks_land_past(level, self.block_order) != axis {
return false;
}
if level.axes().into_iter().any(|axis| space.is_dynamic(axis)) {
return true;
}
let (x, y) = self.grid(level, space);
x * y * self.planes_per_cube > self.blocks(level, space) * self.block_planes()
}
fn blocks(&self, level: &Level, space: &Space) -> usize {
level
.axes()
.into_iter()
.map(|axis| level.tiles(space, axis).div_ceil(self.block_along(axis)))
.product()
}
fn spare_blocks_land_past(level: &Level, order: CubeOrder) -> Axis {
let axes = level.axes();
match axes.len() {
1 => axes[0],
2 => match order.canonicalize() {
CubeOrder::SwizzleRow(_) => axes[0],
CubeOrder::RowMajor | CubeOrder::SwizzleCol(_) => axes[1],
},
_ => axes[axes.len() - 1],
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct UnitCube {
pub plane_width: usize,
pub planes: usize,
}
impl UnitCube {
pub fn units(&self) -> usize {
self.plane_width * self.planes
}
pub(crate) fn order(&self) -> PlaneOrder {
PlaneOrder::new(&[], CubeOrder::RowMajor).cubes_of(self.units())
}
}
#[cube]
pub(crate) fn launch_tiles(
#[comptime] space: Space,
#[comptime] level: Level,
grid: &Coords<usize>,
) -> (Coords<usize>, Coords<usize>) {
let order = comptime!(
level
.launch_order()
.expect("a level spanning the launch states the order its instances take")
);
let in_cube = match comptime!(level.coverage().scope()) {
Some(ComputeScope::Unit) => UNIT_POS as usize,
_ => UNIT_POS_Y as usize,
};
let rank = comptime!(space.rank());
let named = comptime!(
level
.axes()
.into_iter()
.map(|axis| space.position(axis))
.collect::<Vec<_>>()
);
let cube = CubeAxis::position(CubeAxis::X)
.plus(CubeAxis::position(CubeAxis::Y).times(CUBE_COUNT_X as usize));
let (block, local) = if comptime!(order.one_block_a_cube()) {
(cube, in_cube)
} else {
let flat = cube
.times(comptime!(order.planes_per_cube()).runtime())
.plus(in_cube);
let planes = comptime!(order.block_planes()).runtime();
(flat.divided_by(planes), flat.remainder(planes))
};
let mut blocks = Coords::<usize>::new();
#[unroll]
for p in comptime!(named.clone()) {
let along = comptime!(order.block_along(space.axis_at(p)));
blocks.push(
grid.at(p)
.plus(comptime!(along - 1).runtime())
.divided_by(along.runtime()),
);
}
let at = block_coords(
block,
&blocks,
comptime!(named.len()),
comptime!(order.block_order()),
);
let mut positions = Coords::<usize>::new();
let mut counts = Coords::<usize>::new();
#[unroll]
for p in 0..rank {
match comptime!(named.iter().position(|&q| q == p)) {
None => {
positions.push(0usize);
counts.push(grid.at(p));
}
Some(i) => {
let along = comptime!(order.block_along(space.axis_at(p)));
let inner = comptime!(
named[i + 1..]
.iter()
.map(|&q| order.block_along(space.axis_at(q)))
.product::<usize>()
);
let within = local.divided_by(inner.runtime()).remainder(along.runtime());
let tile = at.at(i).times(along.runtime()).plus(within);
positions.push(tile);
counts.push(1usize);
}
}
}
(positions, counts)
}
#[cube]
fn block_coords(
block: usize,
blocks: &Coords<usize>,
#[comptime] named: usize,
#[comptime] order: CubeOrder,
) -> Coords<usize> {
let mut out = Coords::<usize>::new();
if comptime!(named == 1) {
out.push(block);
} else {
let plane = blocks.at(0).times(blocks.at(1));
let first_two = if comptime!(named == 2) {
block
} else {
block.remainder(plane)
};
let (x, y) = cube_positions(first_two, (blocks.at(0), blocks.at(1)), order);
out.push(x);
out.push(y);
let mut rest = block.divided_by(plane);
#[unroll]
for i in 2..named {
if comptime!(i + 1 == named) {
out.push(rest);
} else {
out.push(rest.remainder(blocks.at(i)));
rest = rest.divided_by(blocks.at(i));
}
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Levels, Partitioning};
const N: Axis = Axis(0);
fn counts(partitioning: &Partitioning) -> (u32, u32, u32) {
let CubeCount::Static(x, y, z) = partitioning.cube_count() else {
unreachable!("a static space lays a static grid");
};
(x, y, z)
}
fn one_plane_a_cube(columns: usize) -> Partitioning {
Partitioning::new(
Space::new(&[(N, columns)]),
Levels::leaf(&[(N, 2)])
.launch_planes(&[N], PlaneOrder::new(&[(N, 1)], CubeOrder::RowMajor))
.build(),
)
}
#[test]
fn a_spill_divides_the_cubes_where_it_can() {
let vocabulary = one_plane_a_cube(151_936);
assert_eq!(counts(&vocabulary), (37_984, 2, 1));
assert!(!vocabulary.overhangs(N));
let prime = one_plane_a_cube(2 * 65_537);
assert_eq!(counts(&prime), (CUBES_PER_GRID_DIMENSION as u32, 2, 1));
assert!(prime.overhangs(N));
}
#[test]
fn a_grid_one_dimension_holds_is_one_row() {
assert_eq!(counts(&one_plane_a_cube(2 * 1000)), (1000, 1, 1));
}
}