1use super::{ComputeScope, Count, CubeAxis, CubeOrder, GridCount, PlaneOrder, UnitCube};
4use crate::{Axis, AxisMap, Extent, Space};
5
6#[derive(Clone, PartialEq, Eq, Hash, Debug)]
9pub struct Level {
10 coverage: Coverage,
11 cuts: AxisMap<Cut>,
12 batches: Vec<Axis>,
14 shared_by: Option<usize>,
15 fillers: usize,
16 order: CubeOrder,
17 launch_span: Option<LaunchSpan>,
20 reversed: Vec<Axis>,
22}
23
24#[derive(Clone, PartialEq, Eq, Hash, Debug)]
26pub(crate) enum LaunchSpan {
27 Planes(PlaneOrder),
29 Units(UnitCube),
32}
33
34#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
36pub(crate) struct Cut {
37 pub(crate) tile: usize,
38 pub(crate) count: Count,
39 pub(crate) spread: Spread,
40}
41
42#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
44pub enum Coverage {
45 Walk,
47 Distribute(ComputeScope),
49}
50
51impl Coverage {
52 pub fn scope(self) -> Option<ComputeScope> {
54 match self {
55 Coverage::Walk => None,
56 Coverage::Distribute(scope) => Some(scope),
57 }
58 }
59}
60
61#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
63pub enum Spread {
64 #[default]
66 Contiguous,
67 Interleaved,
69}
70
71impl Level {
72 pub fn every(tile: &[(Axis, usize)]) -> Level {
74 let cuts: Vec<_> = tile
75 .iter()
76 .map(|&(axis, tile)| (axis, tile, Count::All, Spread::Contiguous))
77 .collect();
78 Level::new(Coverage::Walk, &cuts)
79 }
80
81 pub(crate) fn new(coverage: Coverage, cuts: &[(Axis, usize, Count, Spread)]) -> Level {
83 for (i, &(axis, ..)) in cuts.iter().enumerate() {
84 assert!(
85 !cuts[..i].iter().any(|&(a, ..)| a == axis),
86 "Level: {axis:?} is named twice; a level states each of its axes once"
87 );
88 }
89 for &(axis, tile, count, _) in cuts {
90 assert!(tile > 0, "Level: {axis:?} has a tile of nothing");
91 match (coverage, count) {
92 (
93 Coverage::Distribute(ComputeScope::Cube)
94 | Coverage::Distribute(ComputeScope::Plane),
95 Count::AllAcross(_),
96 ) => {}
97 (_, Count::AllAcross(_)) => {
98 panic!(
99 "Level: {axis:?} is distributed across workers in runs, which only cubes \
100 and planes take"
101 )
102 }
103 (
104 Coverage::Distribute(ComputeScope::Unit)
105 | Coverage::Distribute(ComputeScope::Plane)
106 | Coverage::Distribute(ComputeScope::PlaneGroup { .. }),
107 Count::All,
108 ) => panic!(
109 "Level: {axis:?} takes every tile on a plane's workers, whose count is the \
110 device's; state how many"
111 ),
112 (Coverage::Distribute(ComputeScope::Unit), Count::Distributed(_)) => assert!(
113 matches!(
114 cuts.iter().find(|&&(a, ..)| a == axis).map(|&(.., s)| s),
115 Some(Spread::Interleaved)
116 ),
117 "Level: {axis:?} is distributed in turns to however many units the launch runs, so \
118 the units take it interleaved"
119 ),
120 (_, Count::Distributed(_)) => panic!(
121 "Level: {axis:?} is distributed in turns to however many units the launch runs, \
122 which only a plane's units can be"
123 ),
124 _ => {}
125 }
126 }
127 let distributed = cuts
128 .iter()
129 .any(|&(_, _, count, _)| matches!(count, Count::Distributed(_)));
130 assert!(
131 !distributed
132 || (coverage == Coverage::Distribute(ComputeScope::Unit) && cuts.len() == 1),
133 "Level: an axis distributed to however many units the launch runs takes all of them, so \
134 the level distributes no other axis to the units; it distributes {}",
135 cuts.len()
136 );
137 let cuts: Vec<_> = cuts
138 .iter()
139 .map(|&(axis, tile, count, spread)| {
140 (
141 axis,
142 Cut {
143 tile,
144 count,
145 spread,
146 },
147 )
148 })
149 .collect();
150 Level {
151 coverage,
152 cuts: AxisMap::new(&cuts),
153 batches: Vec::new(),
154 shared_by: None,
155 fillers: 0,
156 order: CubeOrder::RowMajor,
157 launch_span: None,
158 reversed: Vec::new(),
159 }
160 }
161
162 pub(crate) fn spanning_launch(tile: &[(Axis, usize)], order: PlaneOrder) -> Level {
166 assert!(
167 !tile.is_empty(),
168 "Level::spanning_launch: the launch's planes take the tiles of at least one axis"
169 );
170 for axis in order.block_axes() {
171 assert!(
172 tile.iter().any(|&(a, _)| a == axis),
173 "Level::spanning_launch: the order's block names {axis:?}, which the level does not"
174 );
175 }
176 assert!(
177 tile.len() > 1 || !order.block_order().swizzles(),
178 "Level::spanning_launch: the blocks of one axis have one order; a swizzle takes two"
179 );
180 let cuts: Vec<_> = tile
181 .iter()
182 .map(|&(axis, tile)| {
183 assert!(tile > 0, "Level: {axis:?} has a tile of nothing");
184 (
185 axis,
186 Cut {
187 tile,
188 count: Count::All,
189 spread: Spread::Contiguous,
190 },
191 )
192 })
193 .collect();
194 Level {
195 coverage: Coverage::Distribute(ComputeScope::Plane),
196 cuts: AxisMap::new(&cuts),
197 batches: Vec::new(),
198 shared_by: None,
199 fillers: 0,
200 order: CubeOrder::RowMajor,
201 launch_span: Some(LaunchSpan::Planes(order)),
202 reversed: Vec::new(),
203 }
204 }
205
206 pub(crate) fn units_spanning_launch(tile: &[(Axis, usize)], cube: UnitCube) -> Level {
210 assert!(
211 cube.units() > 0,
212 "Level::units_spanning_launch: a cube holds at least one unit"
213 );
214 let order = cube.order();
215 Level {
216 coverage: Coverage::Distribute(ComputeScope::Unit),
217 launch_span: Some(LaunchSpan::Units(cube)),
218 ..Level::spanning_launch(tile, order)
219 }
220 }
221
222 pub fn plane_order(&self) -> Option<&PlaneOrder> {
225 match &self.launch_span {
226 Some(LaunchSpan::Planes(order)) => Some(order),
227 _ => None,
228 }
229 }
230
231 pub fn unit_cube(&self) -> Option<UnitCube> {
233 match self.launch_span {
234 Some(LaunchSpan::Units(cube)) => Some(cube),
235 _ => None,
236 }
237 }
238
239 pub(crate) fn launch_order(&self) -> Option<PlaneOrder> {
242 match &self.launch_span {
243 Some(LaunchSpan::Planes(order)) => Some(order.clone()),
244 Some(LaunchSpan::Units(cube)) => Some(cube.order()),
245 None => None,
246 }
247 }
248
249 pub fn spans_launch(&self) -> bool {
251 self.launch_span.is_some()
252 }
253
254 pub(crate) fn batching(mut self, axes: &[Axis]) -> Level {
256 assert!(
257 self.coverage == Coverage::Distribute(ComputeScope::Cube),
258 "Level::batching: batches ride cubes; this level's tiles are taken by {:?}",
259 self.coverage
260 );
261 for &axis in axes {
262 self.push(
263 axis,
264 Cut {
265 tile: 1,
266 count: Count::All,
267 spread: Spread::Contiguous,
268 },
269 );
270 self.batches.push(axis);
271 }
272 self
273 }
274
275 pub(crate) fn sharing(mut self, n: usize) -> Level {
277 match self.coverage {
278 Coverage::Distribute(ComputeScope::Cube)
279 | Coverage::Distribute(ComputeScope::Plane) => {}
280 Coverage::Distribute(ComputeScope::PlaneGroup { .. }) => panic!(
281 "Level::sharing: a plane group's partials of one box are not merged yet; deal \
282 each group a box of its own"
283 ),
284 Coverage::Distribute(ComputeScope::Unit) => panic!(
285 "Level::sharing: the plane's units combine in registers, which needs them in \
286 lockstep, and units holding different shares never are"
287 ),
288 Coverage::Walk => panic!("Level::sharing: a walk has no workers to share its tiles"),
289 }
290 assert!(
291 self.shared_by.is_none(),
292 "Level::sharing: this level already cuts its tiles as one"
293 );
294 for axis in self.axes() {
295 let cut = self.cuts.get(axis);
296 assert!(
297 cut.count == Count::All && cut.spread == Spread::Contiguous,
298 "Level::sharing: {axis:?} states a count or a spread of its own, which a share \
299 of the whole has no use for"
300 );
301 }
302 self.shared_by = Some(n);
303 self
304 }
305
306 pub(crate) fn filling(mut self, n: usize) -> Level {
308 assert!(
309 self.coverage == Coverage::Walk,
310 "Level::filling: only a walk's regions are staged, and this level's tiles are taken \
311 by {:?}",
312 self.coverage
313 );
314 self.fillers = n;
315 self
316 }
317
318 pub(crate) fn distributed_in(mut self, order: CubeOrder) -> Level {
320 assert!(
321 self.coverage == Coverage::Distribute(ComputeScope::Cube),
322 "Level::distributed_in: only a cube level is distributed to a grid, and this level's tiles are \
323 taken by {:?}",
324 self.coverage
325 );
326 self.order = order.canonicalize();
327 self
328 }
329
330 pub(crate) fn reversed(mut self, axes: &[Axis]) -> Level {
333 assert!(
334 self.coverage == Coverage::Distribute(ComputeScope::Cube) || self.spans_launch(),
335 "Level::reversed: only a cube level or a level spanning the launch hands its tiles out \
336 in a launch order, and this level's tiles are taken by {:?}",
337 self.coverage
338 );
339 assert!(
340 self.shared_by.is_none(),
341 "Level::reversed: this level cuts its tiles as one index, which has no axis to reverse"
342 );
343 for &axis in axes {
344 assert!(
345 self.cuts.contains(axis),
346 "Level::reversed: {axis:?} is not an axis of this level"
347 );
348 assert!(
349 !self.reversed.contains(&axis),
350 "Level::reversed: {axis:?} is reversed twice"
351 );
352 self.reversed.push(axis);
353 }
354 self
355 }
356
357 fn push(&mut self, axis: Axis, cut: Cut) {
358 assert!(
359 !self.cuts.contains(axis),
360 "Level: {axis:?} is named twice; a level states each of its axes once"
361 );
362 let mut cuts: Vec<_> = self
363 .axes()
364 .into_iter()
365 .map(|a| (a, self.cuts.get(a)))
366 .collect();
367 cuts.push((axis, cut));
368 self.cuts = AxisMap::new(&cuts);
369 }
370
371 pub fn coverage(&self) -> Coverage {
373 self.coverage
374 }
375
376 pub fn axes(&self) -> Vec<Axis> {
378 (0..self.cuts.len()).map(|i| self.cuts.axis_at(i)).collect()
379 }
380
381 pub fn tile(&self, axis: Axis) -> Option<usize> {
383 self.cuts.contains(axis).then(|| self.cuts.get(axis).tile)
384 }
385
386 pub fn count(&self, axis: Axis) -> Option<Count> {
388 self.cuts.contains(axis).then(|| self.cuts.get(axis).count)
389 }
390
391 pub fn spread(&self, axis: Axis) -> Option<Spread> {
393 self.cuts.contains(axis).then(|| self.cuts.get(axis).spread)
394 }
395
396 pub(crate) fn cut(&self, axis: Axis) -> Cut {
397 self.cuts.get(axis)
398 }
399
400 pub fn distributes(&self, axis: Axis) -> bool {
402 self.coverage != Coverage::Walk && self.shared_by.is_none() && self.cuts.contains(axis)
403 }
404
405 pub(crate) fn cube_axis(&self, axis: Axis) -> Option<CubeAxis> {
408 if self.coverage != Coverage::Distribute(ComputeScope::Cube) || !self.cuts.contains(axis) {
409 return None;
410 }
411 if self.batches.contains(&axis) {
412 return Some(CubeAxis::Z);
413 }
414 let dims = [CubeAxis::X, CubeAxis::Y, CubeAxis::Z];
415 let position = self
416 .axes()
417 .into_iter()
418 .filter(|a| !self.batches.contains(a))
419 .position(|a| a == axis)
420 .expect("the axis is named and not a batch");
421 Some(dims[position])
422 }
423
424 pub fn shared_by(&self) -> Option<usize> {
426 self.shared_by
427 }
428
429 pub fn fillers(&self) -> usize {
431 self.fillers
432 }
433
434 pub fn order(&self) -> CubeOrder {
436 self.order
437 }
438
439 pub fn is_reversed(&self, axis: Axis) -> bool {
441 self.reversed.contains(&axis)
442 }
443
444 pub(crate) fn extent_in(&self, space: &Space, axis: Axis) -> Extent {
446 match self.tile(axis) {
447 Some(tile) => Extent::Static(tile),
448 None => space.extent_raw(axis),
449 }
450 }
451
452 pub fn child(&self, space: &Space) -> Space {
454 Space::from_extents(
455 &space
456 .axes()
457 .map(|axis| (axis, self.extent_in(space, axis)))
458 .collect::<Vec<_>>(),
459 )
460 }
461
462 pub(crate) fn overhangs(&self, space: &Space, axis: Axis) -> bool {
465 let Some(tile) = self.tile(axis) else {
466 return false;
467 };
468 let cut = !space.extent(axis).is_multiple_of(tile);
469 match self.launch_order() {
470 None => cut,
471 Some(order) => cut || order.overruns(self, space, axis),
472 }
473 }
474
475 pub(crate) fn tiles(&self, space: &Space, axis: Axis) -> usize {
477 match self.count(axis) {
478 None => 1,
479 Some(count) => count.tiles(space.extent(axis), self.cut(axis).tile),
480 }
481 }
482
483 pub(crate) fn tiles_const(&self, space: &Space, axis: Axis) -> Option<usize> {
485 match self.count(axis) {
486 None => Some(1),
487 Some(count) => count.tiles_const(space.extent_raw(axis), self.cut(axis).tile),
488 }
489 }
490
491 pub(crate) fn grid(&self, axis: Axis) -> GridCount {
493 match self.count(axis) {
494 None => GridCount::Const(1),
495 Some(count) => count.grid(self.cut(axis).tile),
496 }
497 }
498
499 pub(crate) fn instances_along(&self, space: &Space, axis: Axis) -> Option<usize> {
501 match self.count(axis) {
502 None => Some(1),
503 Some(Count::Distributed(_)) => None,
504 Some(count) => match count.stated() {
505 Some(n) => Some(n),
506 None if !space.contains(axis) => None,
507 None => self.tiles_const(space, axis),
508 },
509 }
510 }
511}