Skip to main content

cubecl_tile/tile/kind/memory/
access.rs

1//! Reading, writing and windowing a [`Memory`]: cooperative fills, the views a leaf reads and
2//! writes through, and [`at`](Memory::at).
3//! Cooperative fills assume every unit their destination names ([`FillUnits`]) runs them: the
4//! cube's, or, for a stage one plane owns, that plane's.
5
6use cubecl::prelude::*;
7
8use cubecl_std::tensor::{
9    AsView, AsViewExpand, AsViewMut, AsViewMutExpand, ErasedTensor, View, ViewMut, WriteOnly,
10    layout::{Coordinates, Coords1d, Coords2d, CoordsDyn},
11};
12
13use crate::*;
14
15#[cube]
16impl Tile {
17    /// A read [`View`] over `Vector<T, W>` lines through the base layout and `Window`.
18    /// `W` is the line width (`self.store.vector_size`).
19    pub fn view<T: Numeric, W: Size>(&self) -> View<'_, Vector<T, W>, CoordsDyn> {
20        let g = self.mem("view");
21        if comptime!(g.store.packing != Packing::Plain) {
22            panic!(
23                "Tile::view: a packed tile only serves values its read unpacks (Tile::copy_from, \
24                 Tile::matrix_packed)"
25            )
26        }
27        g.window_view::<T, W>(comptime!(Guard::Checked))
28    }
29
30    /// Scalars of this stage one unit holds in registers across a contraction.
31    #[allow(dead_code)] // Reached through its expand, from `Stages::prefetched`.
32    pub(crate) fn fetched_scalars(&self) -> comptime_type!(usize) {
33        self.mem("fetched_scalars").fetched_scalars()
34    }
35
36    /// Free the shared memory this stage is held in; a stage of a call has none.
37    pub(crate) fn free_stage(&self) {
38        self.mem("free_stage").store.free();
39        self.free_scales();
40    }
41
42    /// This unit's share of filling this stage from `src`, read into `fetched` but not yet written.
43    #[allow(dead_code)] // Reached through its expand, from `Stages::prefetched`.
44    pub(crate) fn fetch_from<T: Numeric, W: Size>(
45        &self,
46        src: &Tile,
47        fetched: &mut Array<Vector<T, W>>,
48    ) {
49        let space = comptime!(self.place.space.clone());
50        self.mem("fetch_from")
51            .fetch_straight(src.mem("fetch_from"), space, fetched);
52    }
53
54    /// Write what [`fetch_from`](Tile::fetch_from) read into this stage.
55    #[allow(dead_code)] // Reached through its expand, from `Stages::prefetched`.
56    pub(crate) fn store_fetched<T: Numeric, W: Size>(&mut self, fetched: &Array<Vector<T, W>>) {
57        self.mem_mut("store_fetched").store_fetched(fetched);
58    }
59
60    pub fn view_mut<T: Numeric, W: Size>(&mut self) -> ViewMut<'_, Vector<T, W>, CoordsDyn> {
61        let g = self.mem_mut("view_mut");
62        g.window_view_mut::<T, W>(comptime!(Guard::Checked))
63    }
64}
65
66#[cube]
67impl Memory {
68    /// State what the accumulation being lowered starts from.
69    pub(crate) fn set_init_from(&mut self, #[comptime] init_from: InitFrom) {
70        comptime!({
71            self.init_from = init_from;
72        });
73    }
74
75    /// State how a contraction into this window runs.
76    pub(crate) fn set_contraction(&mut self, #[comptime] contraction: Contraction) {
77        comptime!({
78            self.contraction = Some(contraction);
79        });
80    }
81
82    /// Zero this window; a checked window skips cells past the logical bound.
83    pub(crate) fn zero<T: Numeric>(&mut self) {
84        self.init::<T>(T::from_int(0));
85    }
86
87    /// Initialize this window with `val`; a checked window skips cells past the logical bound.
88    pub(crate) fn init<T: Numeric>(&mut self, val: T) {
89        let size!(W) = comptime!(self.store.vector_size);
90        let mut d = self.flat_mut::<T, W>();
91        let total = d.shape();
92        for i in 0..total {
93            d.write(i, Vector::<T, W>::cast_from(val));
94        }
95    }
96
97    /// What a stage of this store holds: its values if plain, its words if packed several fields
98    /// a word. Staged as words, a packed operand takes a fraction of the shared memory its values
99    /// would; a field as wide as its word takes the same either way, so it is staged decoded, as
100    /// the values a reader asks for, which a matrix fragment can load where words it cannot.
101    pub(crate) fn stage_element(&self) -> comptime_type!(StageElement) {
102        comptime!(match self.store.packing {
103            Packing::Plain => StageElement::Served,
104            Packing::Packed { field } if field.per_word() == 1 => StageElement::Decoded,
105            Packing::Packed { .. } => StageElement::Stored,
106        })
107    }
108
109    /// How this store's values sit in memory, as stated at construction.
110    pub(crate) fn packing(&self) -> comptime_type!(Packing) {
111        comptime!(self.store.packing)
112    }
113
114    /// This buffer's byte length: the transaction count a TMA fill into it lands.
115    pub(crate) fn size_bytes<T: Numeric>(&self) -> u32 {
116        let wp = comptime!(self.store.packing.physical(self.store.vector_size) as u32);
117        match comptime!(self.store.packing) {
118            Packing::Plain => {
119                self.store.buffer::<T>().len() as u32 * T::size().comptime() as u32 * wp
120            }
121            Packing::Packed { field: _ } => {
122                self.store.buffer::<u32>().len() as u32 * u32::size().comptime() as u32 * wp
123            }
124        }
125    }
126
127    /// The base layout: a `[grid…, tile…]` split (`levels > 0`) or plain strided (`levels = 0`).
128    pub(crate) fn base(&self) -> BufferLayout {
129        self.layout.clone()
130    }
131
132    fn window(&self) -> Window {
133        self.window.clone()
134    }
135
136    /// The window extent, for shape-only readers that must not regroup the buffer.
137    fn extent(&self) -> Coords<u32> {
138        self.window.extent.clone()
139    }
140
141    /// The buffer re-grouped into `Vector<T, W>` lines; buffers only.
142    fn lines<T: Numeric, W: Size>(&self) -> &[Vector<T, W>] {
143        self.store
144            .buffer::<T>()
145            .as_vectorized()
146            .with_vector_size::<W>()
147    }
148
149    /// The mutable twin of [`lines`](Memory::lines); buffers only.
150    fn lines_mut<T: Numeric, W: Size>(&mut self) -> &mut [Vector<T, W>] {
151        self.store
152            .buffer_mut::<T>()
153            .as_vectorized_mut()
154            .with_vector_size_mut::<W>()
155    }
156
157    /// The backing as a [`ViewMut`] addressed by `layout`: the write path.
158    fn write_view<T: Numeric, W: Size>(
159        &mut self,
160        layout: BufferLayout,
161    ) -> ViewMut<'_, Vector<T, W>, CoordsDyn> {
162        // Two sequences may share a page, and an entry past the pool is read as its last.
163        comptime!(assert!(
164            layout.paging.is_none(),
165            "Memory::write_view: a pool of pages is read through its table, never written \
166             through it; write the pool where it lies"
167        ));
168        match &mut self.store.backing {
169            Backing::Buffer(buffer) => buffer
170                .at_mut::<T>()
171                .as_vectorized_mut()
172                .with_vector_size_mut::<W>()
173                .view_mut(layout),
174            Backing::WriteCall(destination) => ViewMut::new::<
175                &mut ErasedTensor<T, WriteOnly>,
176                Coords1d,
177            >(destination.call_mut::<T>(), layout),
178            Backing::ReadCall(_) => panic!(
179                "Memory::write_view: this tile's backing is read through a call, which is \
180                 read-only"
181            ),
182        }
183    }
184
185    /// The backing as a [`View`] addressed by `layout`: the read path.
186    pub(crate) fn read_view<T: Numeric, W: Size>(
187        &self,
188        layout: BufferLayout,
189    ) -> View<'_, Vector<T, W>, CoordsDyn> {
190        match &self.store.backing {
191            Backing::Buffer(buffer) => buffer
192                .at::<T>()
193                .as_vectorized()
194                .with_vector_size::<W>()
195                .view(layout),
196            Backing::ReadCall(producer) => {
197                View::new::<&ErasedTensor<T, ReadOnly>, Coords1d>(producer.call::<T>(), layout)
198            }
199            Backing::WriteCall(_) => panic!(
200                "Memory::read_view: this tile's backing is written through a call, and is never \
201                 read"
202            ),
203        }
204    }
205
206    /// The window as a view over its own coordinates, served at `T` grouped `W` wide.
207    fn window_view<T: Numeric, W: Size>(
208        &self,
209        #[comptime] guard: Guard,
210    ) -> View<'_, Vector<T, W>, CoordsDyn> {
211        match comptime!(self.access.storage) {
212            WindowStorage::Contiguous => {
213                let start = self.window_start.cast::<usize>();
214                let all = self.lines::<T, W>();
215                all.slice(start, all.len()).view(self.contiguous_layout())
216            }
217            WindowStorage::Stored(_) => WindowedView::new(
218                self.read_view::<T, W>(self.base()),
219                self.window().with_guard(guard),
220            )
221            .view(),
222        }
223    }
224
225    /// [`window_view`](Memory::window_view) at the storage element `I`, grouped `WP` wide.
226    pub(crate) fn window_view_storage<I: Numeric, WP: Size>(
227        &self,
228        #[comptime] guard: Guard,
229    ) -> View<'_, Vector<I, WP>, CoordsDyn> {
230        match comptime!(self.access.storage) {
231            WindowStorage::Contiguous => {
232                let start = self.window_start.cast::<usize>();
233                let all = self.lines_storage::<I, WP>();
234                all.slice(start, all.len()).view(self.contiguous_layout())
235            }
236            WindowStorage::Stored(_) => WindowedView::new(
237                self.lines_storage::<I, WP>().view(self.base()),
238                self.window().with_guard(guard),
239            )
240            .view(),
241        }
242    }
243
244    /// The mutable twin of [`window_view`](Memory::window_view).
245    fn window_view_mut<T: Numeric, W: Size>(
246        &mut self,
247        #[comptime] guard: Guard,
248    ) -> ViewMut<'_, Vector<T, W>, CoordsDyn> {
249        match comptime!(self.access.storage) {
250            WindowStorage::Contiguous => {
251                let start = self.window_start.cast::<usize>();
252                let layout = self.contiguous_layout();
253                let all = self.lines_mut::<T, W>();
254                let len = all.len();
255                all.slice_mut(start, len).view_mut(layout)
256            }
257            WindowStorage::Stored(_) => {
258                let base = self.base();
259                let window = self.window().with_guard(guard);
260                WindowedViewMut::new(self.write_view::<T, W>(base), window).view_mut()
261            }
262        }
263    }
264
265    /// The layout of a window inside one storage tile, relative to its origin.
266    fn contiguous_layout(&self) -> BufferLayout {
267        comptime!(assert!(
268            !self.access.overhang.masks(),
269            "Memory: a window inside one storage tile reads unmasked; a storage-tiled tensor is \
270             padded to whole storage tiles"
271        ));
272        comptime!(self.layout.rows.assert_in_order(
273            "Memory::contiguous_layout",
274            "a window inside one storage tile is addressed as one run"
275        ));
276        let positional = comptime!(self.layout.projection.clone());
277        let rank = comptime!(positional.coordinate_rank());
278        let mut strides = Coords::<u32>::new();
279        #[unroll]
280        for c in 0..rank {
281            let axis = comptime!(positional.logical_axes()[c]);
282            let inner = comptime!(*positional.carriers(axis).last().unwrap());
283            strides.push(self.layout.physical_strides.at(inner));
284        }
285        BufferLayout {
286            physical_shape: self.window.extent.clone(),
287            physical_strides: strides,
288            projection: comptime!(Projection::direct(positional.logical_axes())),
289            rows: RowArrangement::InOrder,
290            paging: ComptimeOption::new_None(),
291        }
292    }
293
294    /// [`lines`](Memory::lines) re-typed to the storage element `I` (`u32` words when packed).
295    fn lines_storage<I: Numeric, W: Size>(&self) -> &[Vector<I, W>] {
296        self.store
297            .buffer::<I>()
298            .as_vectorized()
299            .with_vector_size::<W>()
300    }
301
302    /// The mutable twin of [`lines_storage`](Memory::lines_storage).
303    pub(crate) fn lines_storage_mut<I: Numeric, W: Size>(&mut self) -> &mut [Vector<I, W>] {
304        self.store
305            .buffer_mut::<I>()
306            .as_vectorized_mut()
307            .with_vector_size_mut::<W>()
308    }
309
310    /// The buffer from the window origin on, rows stepping by [`row_stride`](Memory::row_stride).
311    pub(crate) fn window_slice<T: Numeric>(&self) -> &[T] {
312        let offset = self.window_offset();
313        self.store
314            .buffer::<T>()
315            .slice(offset, self.store.buffer::<T>().len())
316    }
317
318    /// The mutable twin of [`window_slice`](Memory::window_slice).
319    pub(crate) fn window_slice_mut<T: Numeric>(&mut self) -> &mut [T] {
320        let offset = self.window_offset();
321        let end = self.store.buffer::<T>().len();
322        self.store.buffer_mut::<T>().slice_mut(offset, end)
323    }
324
325    /// Line offset of the window origin; the window must be one contiguous region.
326    fn window_offset(&self) -> usize {
327        // A base and a row stride step past a page into whichever page lies next in the pool.
328        // Ahead of the overhang's: a pool's table-set extents always mask.
329        comptime!(assert!(
330            self.layout.paging.is_none(),
331            "Memory::window_offset: a pool of pages is read through its table one line at a \
332             time, never as one raw run; stage it"
333        ));
334        comptime!(assert!(
335            !self.access.overhang.masks(),
336            "Memory::window_offset: cmma cannot mask an overhang"
337        ));
338        // A base and row stride above a storage tile would silently read another tile's cells.
339        match comptime!(self.access.storage) {
340            WindowStorage::Stored(Storage::Strided) => {}
341            WindowStorage::Contiguous => {}
342            WindowStorage::Stored(Storage::Tiled(Some(level))) => panic!(
343                "Memory::window_offset: this window sits above its storage tile (the tile of \
344                 level {level}), spanning several, so it is not one contiguous region; descend \
345                 through that level first, or read the operand through its layout"
346            ),
347            WindowStorage::Stored(Storage::Tiled(None)) => panic!(
348                "Memory::window_offset: this operand's storage tile is the tile of no level of \
349                 the kernel's nest, so no window is known to lie inside one; read it through its \
350                 layout, or stage it"
351            ),
352        }
353        // A raw window addresses a row as a base and a stride, which a swizzled row is not.
354        comptime!(
355            self.layout
356                .rows
357                .assert_rows_are_runs("Memory::window_slice")
358        );
359        // A raw window would hand a packed store's words over as served values.
360        if comptime!(self.store.packing != Packing::Plain) {
361            panic!(
362                "Memory::window_slice: a packed store has no raw element window; a fragment \
363                 load reads it through Tile::matrix_packed"
364            )
365        }
366        self.window_start.cast::<usize>()
367    }
368
369    /// Scalar stride between matrix rows, `row` the dim a row steps along (direct, untiled stores;
370    /// any other steps along its second-to-last dim).
371    pub(crate) fn row_stride_at(&self, #[comptime] row: usize) -> u32 {
372        let rank = comptime!(self.layout.projection.physical_rank());
373        let row = comptime!(
374            if self.projection.is_direct() && !self.layout.projection.is_tiled() {
375                row
376            } else {
377                rank - 2
378            }
379        );
380        self.layout
381            .physical_strides
382            .at(row)
383            .times(comptime!(self.store.vector_size as u32).runtime())
384    }
385
386    /// Re-view this buffer through `layout` as a [`Masked`] carrying its own mask flag.
387    pub(crate) fn masked<T: Numeric, W: Size, C: Coordinates, L: TileLayout<C>>(
388        &self,
389        layout: L,
390        #[comptime] guard: Guard,
391    ) -> Masked<'_, Vector<T, W>, C> {
392        if comptime!(self.store.packing != Packing::Plain) {
393            panic!(
394                "Tile::matrix: a packed tile only serves values its read unpacks \
395                 (Tile::matrix_packed)"
396            )
397        }
398        Masked::new(
399            self.window_view::<T, W>(guard).view(layout),
400            comptime!(guard.checks() && self.access.overhang.masks()),
401        )
402    }
403
404    /// The mask flag for a write view; refuses a [`Boundary::Clamp`] operand, whose writes alias.
405    fn write_check(&self) -> comptime_type!(bool) {
406        // Whole-operand on purpose: one clamped axis already lands two cells on one.
407        comptime!(assert!(
408            !self.window.boundaries.contains(&Some(Boundary::Clamp))
409                || !self.access.overhang.masks(),
410            "Memory: a Boundary::Clamp operand is read-only, a clamped write aliases the edge cell"
411        ));
412        comptime!(self.access.overhang.masks())
413    }
414
415    /// The mutable twin of [`masked`](Memory::masked).
416    pub(crate) fn masked_mut<T: Numeric, W: Size, C: Coordinates, L: TileLayout<C>>(
417        &mut self,
418        layout: L,
419    ) -> MaskedMut<'_, Vector<T, W>, C> {
420        if comptime!(self.store.packing != Packing::Plain) {
421            panic!("Tile::matrix_mut: writing a packed tile requires repacking")
422        }
423        let check = self.write_check();
424        MaskedMut::new(
425            self.window_view_mut::<T, W>(comptime!(Guard::Checked))
426                .view_mut(layout),
427            check,
428        )
429    }
430
431    /// Re-view this buffer as a flat 1-D [`FlatView`] over its [`Window`] extent.
432    pub(crate) fn flat<T: Numeric, W: Size>(&self) -> FlatView<'_, Vector<T, W>> {
433        FlatView::new(
434            self.window_view::<T, W>(comptime!(Guard::Checked))
435                .view(FlatLayout::new(self.window.extent.clone())),
436            comptime!(self.access.overhang.masks()),
437        )
438    }
439
440    /// [`flat`](Memory::flat) unpacked through [`Packing`]; `WP` is the physical line.
441    pub(crate) fn flat_unpacked<T: Numeric, WP: Size, W: Size>(
442        &self,
443    ) -> FlatView<'_, Vector<T, W>> {
444        self.unpacked::<T, WP, W, Coords1d, FlatLayout>(
445            FlatLayout::new(self.window.extent.clone()),
446            comptime!(Guard::Checked),
447        )
448    }
449
450    /// [`masked`](Memory::masked) unpacked through [`Packing`].
451    /// `WP` is the physical line, `W` the served one.
452    pub(crate) fn unpacked<
453        T: Numeric,
454        WP: Size,
455        W: Size,
456        C: Coordinates + 'static,
457        L: TileLayout<C>,
458    >(
459        &self,
460        layout: L,
461        #[comptime] guard: Guard,
462    ) -> Masked<'_, Vector<T, W>, C> {
463        let packing = self.packing();
464        match comptime!(packing) {
465            Packing::Plain => self.masked::<T, W, C, L>(layout, guard),
466            Packing::Packed { field } => {
467                let words = self
468                    .window_view_storage::<u32, WP>(comptime!(Guard::Checked))
469                    .view(layout);
470                let values = PackedView::<WP, T, W, C>::new(words, comptime!(field));
471                Masked::new(
472                    values.view(),
473                    comptime!(guard.checks() && self.access.overhang.masks()),
474                )
475            }
476        }
477    }
478
479    /// [`unpacked`](Memory::unpacked) with the physical line taken from this store's [`Packing`].
480    pub(crate) fn packed<T: Numeric, W: Size, C: Coordinates + 'static, L: TileLayout<C>>(
481        &self,
482        layout: L,
483        #[comptime] guard: Guard,
484    ) -> Masked<'_, Vector<T, W>, C> {
485        let packing = self.packing();
486        let physical = comptime!(packing.physical(self.store.vector_size));
487        // `size!` binds inside each arm: hoisted, a packed read lands on the wrong field.
488        match comptime!(packing) {
489            Packing::Plain => {
490                let size!(WP) = physical;
491                self.unpacked::<T, WP, W, C, L>(layout, guard)
492            }
493            Packing::Packed { field: _ } => {
494                let size!(WP) = physical;
495                self.unpacked::<T, WP, W, C, L>(layout, guard)
496            }
497        }
498    }
499
500    /// The raw words of a packed store over the tile's whole logical box.
501    pub(crate) fn nd_words<WP: Size>(
502        &self,
503        layout: ProjectionInKernel,
504        #[comptime] guard: Guard,
505    ) -> Masked<'_, Vector<u32, WP>, CoordsDyn> {
506        comptime!(assert!(
507            matches!(self.store.packing, Packing::Packed { .. }),
508            "Memory::nd_words: only a packed store holds words"
509        ));
510        let words = self.window_view_storage::<u32, WP>(guard).view(layout);
511        Masked::new(
512            words,
513            comptime!(guard.checks() && self.access.overhang.masks()),
514        )
515    }
516
517    /// The mutable twin of [`nd_words`](Memory::nd_words): a packed store's raw words, each written
518    /// whole by a writer that packed every field of it.
519    pub(crate) fn nd_words_mut<WP: Size>(
520        &mut self,
521        layout: ProjectionInKernel,
522    ) -> MaskedMut<'_, Vector<u32, WP>, CoordsDyn> {
523        comptime!(assert!(
524            matches!(self.store.packing, Packing::Packed { .. }),
525            "Memory::nd_words_mut: only a packed store holds words"
526        ));
527        let check = self.write_check();
528        MaskedMut::new(
529            self.window_view_mut::<u32, WP>(comptime!(Guard::Checked))
530                .view_mut(layout),
531            check,
532        )
533    }
534
535    /// The identity step over this window's physical box, for callers that evaluate the map themselves.
536    /// It masks against the physical box only: the caller owes in-range logical coordinates.
537    pub(crate) fn physical_box(&self) -> CompactionStep {
538        let rank = comptime!(self.projection.physical_rank());
539        CompactionStep::new(self.window.extent.clone(), comptime!(vec![1; rank]))
540    }
541
542    /// The `i`-th batch matrix of this window over `axes`, through the operand's mapping.
543    pub(crate) fn batch_matrix(
544        &self,
545        #[comptime] space: Space,
546        #[comptime] axes: MatrixAxes,
547        i: usize,
548    ) -> ProjectedMatrix {
549        let bound = self.extent();
550        let load = self.vector_tile(&space);
551        ProjectedMatrix::new(
552            TileMatrix::batch(
553                &bound,
554                comptime!(&space),
555                comptime!(self.projection.composition()),
556                comptime!(&load),
557                axes,
558                i,
559            ),
560            self.axis_projection(comptime!(space.clone())),
561        )
562    }
563
564    /// This window's whole logical box as a `rows x cols` matrix, through the operand's mapping.
565    pub(crate) fn whole_matrix(
566        &self,
567        #[comptime] space: Space,
568        #[comptime] rows: usize,
569        #[comptime] cols: usize,
570    ) -> ProjectedMatrix {
571        let vector_size = comptime!(self.store.vector_size);
572        ProjectedMatrix::new(
573            TileMatrix::whole(comptime!(&space), vector_size, rows, cols),
574            axis_projection(
575                comptime!(space),
576                comptime!(self.projection.clone()),
577                self.map.clone(),
578                vector_size,
579            ),
580        )
581    }
582
583    /// The operand's [`Projection`] applied to this window's logical box: the N-D read surface,
584    /// each axis counted in this memory's loads ([`vector_tile`](Memory::vector_tile)).
585    pub(crate) fn axis_projection(&self, #[comptime] space: Space) -> ProjectionInKernel {
586        let load = self.vector_tile(&space);
587        ProjectionInKernel::new(
588            Coords::constant(comptime!(load.counts(&space))),
589            self.map.clone(),
590            comptime!(space.clone()),
591            comptime!(self.projection.clone()),
592            comptime!(load.values()),
593        )
594    }
595
596    /// The mutable twin of [`flat`](Memory::flat).
597    pub(crate) fn flat_mut<T: Numeric, W: Size>(&mut self) -> FlatViewMut<'_, Vector<T, W>> {
598        if comptime!(self.store.packing != Packing::Plain) {
599            panic!("Tile::flat_mut: writing a packed tile requires repacking")
600        }
601        let extent = self.window.extent.clone();
602        let check = self.write_check();
603        FlatViewMut::new(
604            self.window_view_mut::<T, W>(comptime!(Guard::Checked))
605                .view_mut(FlatLayout::new(extent)),
606            check,
607        )
608    }
609
610    /// The `i`-th batch matrix as a writable 2-D view.
611    pub(crate) fn matrix_mut<T: Numeric, W: Size>(
612        &mut self,
613        i: usize,
614        #[comptime] axes: MatrixAxes,
615        #[comptime] space: Space,
616    ) -> MatrixViewMut<'_, Vector<T, W>> {
617        // Only an overlapping map aliases a cell under a write.
618        comptime!(assert!(
619            self.projection.composition() != Composition::Overlapping,
620            "Memory::matrix_mut: an overlapping operand aliases under a write"
621        ));
622        let layout = self.batch_matrix(space, axes, i);
623        self.masked_mut::<T, W, Coords2d, ProjectedMatrix>(layout)
624    }
625
626    /// The [`AccumulateView`] over batch matrix `i`, carrying its share, [`Monoid`] and init.
627    pub(crate) fn matrix_accumulate<T: Numeric, W: Size>(
628        &mut self,
629        i: usize,
630        #[comptime] axes: MatrixAxes,
631        #[comptime] space: Space,
632        #[comptime] monoid: Monoid,
633    ) -> AccumulateView<'_, T, W> {
634        let unit_share = comptime!(self.unit_share);
635        let split_share = comptime!(self.split_share);
636        let write = comptime!(self.access.write);
637        let init_from = comptime!(self.init_from);
638        AccumulateView::new(
639            self.matrix_mut::<T, W>(i, axes, space),
640            unit_share,
641            split_share,
642            write,
643            monoid,
644            init_from,
645        )
646    }
647
648    /// The [`AccumulateView`] over flat elements.
649    pub(crate) fn flat_accumulate<T: Numeric, W: Size>(
650        &mut self,
651        #[comptime] monoid: Monoid,
652    ) -> AccumulateView<'_, T, W, Coords1d> {
653        // Only under a direct, untiled mapping is the flat logical index the physical cell.
654        comptime!(assert!(
655            !self.layout.projection.is_tiled(),
656            "Memory::flat_accumulate: a storage-tiled window has no flat logical accumulator view"
657        ));
658        comptime!(assert!(
659            self.projection.is_direct(),
660            "Memory::flat_accumulate: a gathered window has no flat logical accumulator view"
661        ));
662        let unit_share = comptime!(self.unit_share);
663        let split_share = comptime!(self.split_share);
664        let write = comptime!(self.access.write);
665        let init_from = comptime!(self.init_from);
666        AccumulateView::new(
667            self.flat_mut::<T, W>(),
668            unit_share,
669            split_share,
670            write,
671            monoid,
672            init_from,
673        )
674    }
675
676    /// Window down to the region `step` names, re-boxing the same buffer; `bound` carries through.
677    pub(crate) fn at(&self, step: &Step, #[comptime] space: Space) -> Memory {
678        let rank = comptime!(self.projection.physical_rank());
679        let (origin, extent, advances, map) = if comptime!(self.projection.is_direct()) {
680            self.direct_descent(step, comptime!(space.clone()))
681        } else {
682            self.gathered_descent(step)
683        };
684        let start = self
685            .window_start
686            .plus(advances.sum(comptime!((0..rank).collect::<Vec<_>>())));
687        // The window's extent along each dim in loads where the level fixes it; it is direct, so
688        // these are the edges `direct_descent` cut.
689        let edges = comptime!(match self.projection.is_direct() {
690            true => {
691                let width = self.store.vector_size;
692                let load = VectorTile::new(
693                    &self.store.stored_tiles,
694                    space.axis_at(space.rank() - 1),
695                    width,
696                )
697                .unwrap_or_else(|why| panic!("Memory: {width} values a load: {why}"));
698                Some(
699                    (0..rank)
700                        .map(|p| {
701                            let axis = space.axis_at(p);
702                            match step.level.extent_in(&space, axis) {
703                                Extent::Dynamic => None,
704                                edge => {
705                                    Some(load.loads_in(axis, edge.get(), space.extent_raw(axis)))
706                                }
707                            }
708                        })
709                        .collect::<Vec<_>>(),
710                )
711            }
712            false => None,
713        });
714        let cut = comptime!(match edges {
715            None => {
716                assert!(
717                    self.layout.paging.is_none(),
718                    "Memory::at: a pool of pages is read through a direct mapping only"
719                );
720                // A gathered origin moves by runtime amounts.
721                WindowCut::Shifted
722            }
723            Some(edges) => {
724                // `space` is the box being cut: a level that does not overhang it keeps its tiles
725                // inside it.
726                let nested = self.layout.paging.is_some()
727                    && (0..rank).all(|p| {
728                        let axis = space.axis_at(p);
729                        edges[p].is_none()
730                            || (matches!(space.extent_raw(axis), Extent::Static(_))
731                                && !step.level.overhangs(&space, axis))
732                    });
733                WindowCut::Level { edges, nested }
734            }
735        });
736
737        self.moved_to(
738            Window::new(
739                origin,
740                extent,
741                self.window.bound.clone(),
742                comptime!(self.window.signed),
743                comptime!(self.window.boundaries.clone()),
744            ),
745            comptime!(cut),
746            start,
747            map,
748            // No longer covers the buffer, so no straight-through fill.
749            comptime!(Access {
750                whole: false,
751                overhang: self.access.overhang,
752                write: self.access.write,
753                fill: self.access.fill,
754                storage: self.access.storage.below(step.depth, &step.level, &space),
755                delivery: self.access.delivery,
756            }),
757            comptime!(UnitShare::new(&step.level, &space).under(self.unit_share)),
758            // Per level: the level's space still has the axis the projection dropped.
759            comptime!(SplitShare::new(&step.level, &step.space, &space).under(self.split_share)),
760        )
761    }
762
763    /// One level down under the direct mapping: per-axis origin, extent, advance, and integral map.
764    fn direct_descent(
765        &self,
766        step: &Step,
767        #[comptime] space: Space,
768    ) -> (Coords<i32>, Coords<u32>, Coords<u32>, RuntimeMap) {
769        let mut origin = Coords::<i32>::new();
770        let mut extent = Coords::<u32>::new();
771        let mut advances = Coords::<u32>::new();
772        let rank = comptime!(self.projection.physical_rank());
773        let load = self.vector_tile(&space);
774
775        #[unroll]
776        for p in 0..rank {
777            let axis = space.axis_at(p);
778            // A whole axis over a dynamic extent carries through unmoved.
779            if comptime!(matches!(
780                step.level.extent_in(&space, axis),
781                Extent::Dynamic
782            )) {
783                origin.push(self.window.origin.at(p));
784                extent.push(self.window.extent.at(p));
785                advances.push(0u32);
786            } else {
787                // The edge counts loads: along an axis a load spans, over its extent there.
788                let edge = comptime!(load.loads_in(
789                    axis,
790                    step.level.extent_in(&space, axis).get(),
791                    space.extent_raw(axis)
792                ));
793                let index = step.coord(axis);
794                let from = self.window.origin.at(p);
795                let moved = index.times(edge).cast::<u32>();
796
797                origin.push(from.plus(moved.cast::<i32>()));
798                extent.push(comptime!(edge as u32).runtime());
799                let projection = comptime!(self.layout.projection.clone());
800                let coordinate = comptime!(Axis(p as u8));
801                let shape = &self.layout.physical_shape;
802                let strides = &self.layout.physical_strides;
803                // Over a storage-tiled axis the offset is not linear in the coordinate: a step
804                // into the next stored tile lands at that tile's stride, so the advance is the
805                // difference of the two offsets.
806                let advance = if comptime!(projection.carriers(coordinate).len() > 1) {
807                    let from = from.cast::<u32>();
808                    axis_offset(
809                        comptime!(projection.clone()),
810                        coordinate,
811                        from.plus(moved),
812                        shape,
813                        strides,
814                    )
815                    .minus(axis_offset(projection, coordinate, from, shape, strides))
816                } else {
817                    index.cast::<u32>().times(axis_offset(
818                        projection,
819                        coordinate,
820                        comptime!(edge as u32).runtime(),
821                        shape,
822                        strides,
823                    ))
824                };
825                advances.push(advance);
826            }
827        }
828        (origin, extent, advances, RuntimeMap::integral(rank))
829    }
830
831    /// [`direct_descent`](Memory::direct_descent) under a gathering mapping.
832    fn gathered_descent(&self, step: &Step) -> (Coords<i32>, Coords<u32>, Coords<u32>, RuntimeMap) {
833        let mut origin = Coords::<i32>::new();
834        let mut extent = Coords::<u32>::new();
835        let mut advances = Coords::<u32>::new();
836        let mut residues = Coords::<u32>::new();
837        let rank = comptime!(self.projection.physical_rank());
838        let w = comptime!(self.store.vector_size);
839
840        #[unroll]
841        for pa in 0..rank {
842            let (moved, residue, span) =
843                gathered_axis_descent(comptime!(self.projection.clone()), step, &self.map, w, pa);
844            origin.push(self.window.origin.at(pa).plus(moved.cast::<i32>()));
845            residues.push(residue);
846            extent.push(span);
847            // `Projection::validate` keeps a gathered operand untiled: one axis step is one stride.
848            advances.push(moved.times(self.layout.physical_strides.at(pa)));
849        }
850        let map = RuntimeMap {
851            coefficients: self.map.coefficients.clone(),
852            residues,
853        };
854        (origin, extent, advances, map)
855    }
856
857    /// This store looking at `window` from line `window_start` on; buffer unchanged, and the layout
858    /// pinned at the window where it reads a pool of pages, cut from this one as `cut` says.
859    #[allow(clippy::too_many_arguments)]
860    fn moved_to(
861        &self,
862        window: Window,
863        #[comptime] cut: WindowCut,
864        window_start: u32,
865        map: RuntimeMap,
866        #[comptime] access: Access,
867        #[comptime] unit_share: UnitShare,
868        #[comptime] split_share: SplitShare,
869    ) -> Memory {
870        let layout = self.layout.pinned_at(&window.origin, cut);
871        self.relaid(
872            layout,
873            window,
874            window_start,
875            map,
876            access,
877            unit_share,
878            split_share,
879        )
880    }
881
882    /// This store taking its pages from `entry` where a cut names its cell
883    /// ([`Tile::with_page_entry`]).
884    pub(crate) fn with_page_entry(&self, entry: &PageEntry) -> Memory {
885        self.relaid(
886            self.layout.with_page_entry(entry),
887            self.window.clone(),
888            self.window_start,
889            self.map.clone(),
890            comptime!(self.access.clone()),
891            comptime!(self.unit_share),
892            comptime!(self.split_share),
893        )
894    }
895
896    /// This store over `layout`, `window` and the rest given, the buffer unchanged.
897    #[allow(clippy::too_many_arguments)]
898    fn relaid(
899        &self,
900        layout: BufferLayout,
901        window: Window,
902        window_start: u32,
903        map: RuntimeMap,
904        #[comptime] access: Access,
905        #[comptime] unit_share: UnitShare,
906        #[comptime] split_share: SplitShare,
907    ) -> Memory {
908        Memory {
909            address: comptime!(self.address),
910            store: self.store.clone(),
911            layout,
912            window,
913            projection: comptime!(self.projection.clone()),
914            source_window: self.source_window.clone(),
915            map,
916            offsets: self.offsets.clone(),
917            window_start,
918            access,
919            unit_share,
920            split_share,
921            init_from: comptime!(self.init_from),
922            contraction: comptime!(self.contraction),
923            codebook: self.codebook.clone(),
924            index: self.index.clone(),
925        }
926    }
927
928    /// This window filled by the units of `holder` where they are fewer than the ones that fill
929    /// its buffer: a plane's window of a buffer the cube fills is the plane's to fill.
930    pub(crate) fn filled_by(&self, #[comptime] holder: ComputeScope) -> Memory {
931        let fill = comptime!(self.access.fill.held_by(holder));
932        self.moved_to(
933            self.window.clone(),
934            comptime!(WindowCut::Unmoved),
935            self.window_start,
936            self.map.clone(),
937            comptime!(Access {
938                fill,
939                ..self.access
940            }),
941            comptime!(self.unit_share),
942            comptime!(self.split_share),
943        )
944    }
945
946    /// This window one wide at `coord` on `axis`, which is not the innermost: what a level cutting
947    /// the axis to one would hand out at that coordinate, read off data instead.
948    #[allow(dead_code)] // Reached through its expand, from [`Tile::indexed_at`](crate::Tile::indexed_at).
949    pub(crate) fn placed_at(&self, #[comptime] axis: Axis, coord: u32) -> Memory {
950        let proj = comptime!(self.projection.clone());
951        let rank = comptime!(proj.physical_rank());
952        let at = comptime!(
953            placed_dim(&proj, axis)
954                .filter(|_| !self.layout.projection.is_tiled())
955                .unwrap_or_else(|| panic!("{}", Refusal::IndexOfAStatedLayout))
956        );
957
958        let mut origin = Coords::<i32>::new();
959        let mut extent = Coords::<u32>::new();
960        #[unroll]
961        for p in 0..rank {
962            if comptime!(p == at) {
963                origin.push(self.window.origin.at(p).plus(coord.cast::<i32>()));
964                extent.push(1u32.runtime());
965            } else {
966                origin.push(self.window.origin.at(p));
967                extent.push(self.window.extent.at(p));
968            }
969        }
970
971        let start = self.window_start.plus(coord.times(axis_offset(
972            comptime!(self.layout.projection.clone()),
973            comptime!(Axis(at as u8)),
974            1u32.runtime(),
975            &self.layout.physical_shape,
976            &self.layout.physical_strides,
977        )));
978
979        self.moved_to(
980            Window::new(
981                origin,
982                extent,
983                self.window.bound.clone(),
984                comptime!(self.window.signed),
985                comptime!(self.window.boundaries.clone()),
986            ),
987            comptime!(WindowCut::Shifted),
988            start,
989            self.map.clone(),
990            comptime!(Access {
991                whole: false,
992                ..self.access
993            }),
994            comptime!(self.unit_share),
995            comptime!(self.split_share),
996        )
997    }
998
999    /// This window placed at element `from` on `axis`, reading up to `until` (zero past it). A
1000    /// `from` known at expansion to be 0 keeps the window's pages.
1001    pub(crate) fn within(&self, #[comptime] axis: Axis, from: usize, until: usize) -> Memory {
1002        let first = from.constant();
1003        let moved = comptime!(first != Some(0));
1004        let proj = comptime!(self.projection.clone());
1005        comptime!(assert!(
1006            proj.untiled().is_direct() && !proj.is_tiled(),
1007            "Memory::within: placing a window at an element needs a direct, untiled mapping"
1008        ));
1009        let rank = comptime!(proj.physical_rank());
1010        let at = comptime!(proj.position(axis));
1011
1012        let mut origin = Coords::<i32>::new();
1013        let mut bound = Coords::<u32>::new();
1014        #[unroll]
1015        for p in 0..rank {
1016            if comptime!(p == at) {
1017                origin.push(self.window.origin.at(p).plus(from.cast::<i32>()));
1018                bound.push(self.window.bound.at(p).min_with(until.cast::<u32>()));
1019            } else {
1020                origin.push(self.window.origin.at(p));
1021                bound.push(self.window.bound.at(p));
1022            }
1023        }
1024
1025        let start = self.window_start.plus(from.cast::<u32>().times(axis_offset(
1026            comptime!(self.layout.projection.clone()),
1027            comptime!(Axis(at as u8)),
1028            1u32.runtime(),
1029            &self.layout.physical_shape,
1030            &self.layout.physical_strides,
1031        )));
1032
1033        self.moved_to(
1034            Window::new(
1035                origin,
1036                self.window.extent.clone(),
1037                bound,
1038                comptime!(self.window.signed),
1039                comptime!({
1040                    let mut boundaries = self.window.boundaries.clone();
1041                    if boundaries.is_empty() {
1042                        boundaries = (0..rank).map(|_| None).collect();
1043                    }
1044                    boundaries[at] = Some(Boundary::Zero);
1045                    boundaries
1046                }),
1047            ),
1048            comptime!(match moved {
1049                true => WindowCut::Shifted,
1050                false => WindowCut::Unmoved,
1051            }),
1052            start,
1053            self.map.clone(),
1054            // `until` is a runtime bound, so the window masks from here down; where it masked
1055            // nothing before, only the run's end needs it.
1056            comptime!(Access {
1057                whole: false,
1058                overhang: match self.access.overhang {
1059                    Overhang::Never | Overhang::Fits => Overhang::Run,
1060                    overhang => overhang,
1061                },
1062                write: self.access.write,
1063                fill: self.access.fill,
1064                storage: self.access.storage,
1065                delivery: self.access.delivery,
1066            }),
1067            comptime!(self.unit_share),
1068            comptime!(self.split_share),
1069        )
1070    }
1071}
1072
1073/// One gathered physical axis's descent: its window move, leftover phase and receptive field.
1074#[cube]
1075fn gathered_axis_descent(
1076    #[comptime] projection: Projection,
1077    cut: &Step,
1078    map: &RuntimeMap,
1079    #[comptime] vector_size: usize,
1080    #[comptime] pa: usize,
1081) -> (u32, u32, u32) {
1082    let axis_map = comptime!(projection.physical_axis(pa));
1083    let n = comptime!(axis_map.terms().len());
1084    let picks = comptime!((0..n).collect::<Vec<_>>());
1085    let lined = comptime!(pa == projection.physical_rank() - 1);
1086
1087    let mut terms = Coords::<u32>::new();
1088    // Receptive-field terms `(edge - 1) * scale`; each branch adds the leading `1`.
1089    let mut spans = Coords::<u32>::new();
1090    #[unroll]
1091    for t in 0..n {
1092        let term = comptime!(axis_map.terms()[t]);
1093        let edge = comptime!(cut.level.extent_in(&cut.space, term.axis).get());
1094        match comptime!(term.scale) {
1095            Scale::Static(s) => {
1096                let step = comptime!(if lined {
1097                    assert!(
1098                        (edge * s).is_multiple_of(vector_size)
1099                            || matches!(cut.space.extent_raw(term.axis), Extent::Static(x) if x == edge),
1100                        "Memory::at: the innermost edge {edge} of {:?} is neither a whole number \
1101                         of {vector_size}-wide lines nor the axis's whole extent, so a step would \
1102                         start mid-line",
1103                        term.axis
1104                    );
1105                    edge * s / vector_size
1106                } else {
1107                    edge * s
1108                });
1109                terms.push(cut.coord(term.axis).times(step).cast::<u32>());
1110                spans.push(comptime!(((edge - 1) * s) as u32).runtime());
1111            }
1112            // No line division: `Projection::validate` keeps the innermost axis a static identity.
1113            Scale::Dynamic { .. } => {
1114                let coefficient = map
1115                    .coefficients
1116                    .at(comptime!(projection.dynamic_scale_index(pa, t).unwrap()));
1117                terms.push(
1118                    cut.coord(term.axis)
1119                        .cast::<u32>()
1120                        .times(comptime!(edge as u32).runtime())
1121                        .times(coefficient),
1122                );
1123                spans.push(comptime!((edge - 1) as u32).runtime().times(coefficient));
1124            }
1125        }
1126    }
1127    let advance = terms.sum(comptime!(picks.clone()));
1128
1129    if comptime!(!axis_map.is_rational()) {
1130        let span = if comptime!(!axis_map.has_dynamic_scale()) {
1131            comptime!({
1132                let s = projection.span(pa, |a| cut.level.extent_in(&cut.space, a).get());
1133                (if lined { s / vector_size } else { s }) as u32
1134            })
1135            .runtime()
1136        } else {
1137            spans.sum(comptime!(picks.clone())).plus(1)
1138        };
1139        (advance, 0u32, span)
1140    } else {
1141        // No `/ vector_size`: `Projection::validate` forces width `1` on a rational innermost axis.
1142        let numerator = advance.plus(map.residues.at(pa));
1143        let field = spans.sum(comptime!(picks.clone()));
1144        match comptime!(axis_map.divisor()) {
1145            Divisor::Static(d) => {
1146                let d = comptime!(d as u32);
1147                let residue = numerator.remainder(d);
1148                (
1149                    numerator.divided_by(d),
1150                    residue,
1151                    field.plus(residue).divided_by(d).plus(1),
1152                )
1153            }
1154            Divisor::Dynamic { .. } => {
1155                let d = map
1156                    .coefficients
1157                    .at(comptime!(projection.dynamic_divisor_index(pa).unwrap()));
1158                let residue = numerator.remainder(d);
1159                (
1160                    numerator.divided_by(d),
1161                    residue,
1162                    field.plus(residue).divided_by(d).plus(1),
1163                )
1164            }
1165        }
1166    }
1167}