1use core::{mem::MaybeUninit, ops::Add};
9
10use crate::copy_plan::{replay_fused_raw, CopyPlan, OverwriteWriter, ReadModifyWrite};
11use crate::inline::InlineVec;
12use crate::raw_ops::{fuse_pair_layout, FusedPairLayout};
13use crate::{Exec, RawStridedMut, RawStridedRef, Result, StridedError, RAW_FUSED_RANK_LIMIT};
14
15type AxisVec<T> = InlineVec<T>;
16
17#[derive(Clone, Debug, Eq, PartialEq)]
29pub struct GatherSpec {
30 pub offset_dims: Vec<usize>,
31 pub collapsed_slice_dims: Vec<usize>,
32 pub start_index_map: Vec<usize>,
33 pub index_vector_dim: usize,
34 pub slice_sizes: Vec<usize>,
35}
36
37pub trait GatherIndex: Copy + Send + Sync {
39 fn to_i64(self) -> i64;
40}
41
42impl GatherIndex for i32 {
43 #[inline]
44 fn to_i64(self) -> i64 {
45 i64::from(self)
46 }
47}
48
49impl GatherIndex for i64 {
50 #[inline]
51 fn to_i64(self) -> i64 {
52 self
53 }
54}
55
56#[derive(Clone, Debug)]
58pub struct GatherPlan {
59 operand_dims: AxisVec<usize>,
60 operand_strides: AxisVec<isize>,
61 index_dims: AxisVec<usize>,
62 index_strides: AxisVec<isize>,
63 dest_dims: AxisVec<usize>,
64 dest_strides: AxisVec<isize>,
65 spec: GatherSpec,
66 replay: GatherReplay,
67 total: usize,
68}
69
70#[derive(Clone, Copy, Debug, Default)]
71struct GatherReplayAxis {
72 dest_step: isize,
73 dest_reset: isize,
74 window_step: isize,
75 window_reset: isize,
76 index_batch_step: isize,
77 index_batch_reset: isize,
78}
79
80#[derive(Clone, Debug)]
81struct GatherReplay {
82 axes: AxisVec<GatherReplayAxis>,
83 index_component_offsets: AxisVec<isize>,
84 index_operand_strides: AxisVec<isize>,
85}
86
87struct GatherReplayState {
88 coords: AxisVec<usize>,
89 dest_offset: isize,
90 window_offset: isize,
91 index_batch_offset: isize,
92}
93
94#[derive(Clone, Copy, Debug, Default)]
95struct WindowReplayAxis {
96 source_step: isize,
97 source_reset: isize,
98 dest_step: isize,
99 dest_reset: isize,
100}
101
102#[derive(Clone, Debug)]
103struct WindowReplay {
104 shape: AxisVec<usize>,
105 axes: AxisVec<WindowReplayAxis>,
106}
107
108struct WindowReplayState {
109 coords: CoordScratch,
110 source_offset: isize,
111 dest_offset: isize,
112}
113
114#[derive(Clone, Debug)]
115struct ScatterReplay {
116 batch: WindowReplay,
117 window: WindowReplay,
118 index_component_offsets: AxisVec<isize>,
119}
120
121impl ScatterReplay {
122 fn compile(
123 batch_shape: &[usize],
124 index_dims: &[usize],
125 index_strides: &[isize],
126 index_vector_dim: usize,
127 index_component_count: usize,
128 update_dims: &[usize],
129 update_strides: &[isize],
130 update_window_dims: &[usize],
131 is_update_window_dim: &[bool],
132 window_shape_updates: &[usize],
133 window_dims: &[usize],
134 dest_strides: &[isize],
135 ) -> Result<Self> {
136 let (
137 batch_source_strides,
138 batch_dest_strides,
139 window_source_strides,
140 window_dest_strides,
141 vector_stride,
142 ) = scatter_replay_strides(
143 index_dims,
144 index_strides,
145 index_vector_dim,
146 update_dims,
147 update_strides,
148 update_window_dims,
149 is_update_window_dim,
150 window_dims,
151 dest_strides,
152 );
153 let batch = WindowReplay::compile(batch_shape, &batch_source_strides, &batch_dest_strides)?;
154 let window = WindowReplay::compile(
155 window_shape_updates,
156 &window_source_strides,
157 &window_dest_strides,
158 )?;
159 let mut index_component_offsets = AxisVec::with_capacity(index_component_count);
160 for component in 0..index_component_count {
161 index_component_offsets.push(checked_offset_add(0, vector_stride, component)?);
162 }
163 Ok(Self {
164 batch,
165 window,
166 index_component_offsets,
167 })
168 }
169}
170
171fn scatter_replay_strides(
172 index_dims: &[usize],
173 index_strides: &[isize],
174 index_vector_dim: usize,
175 update_dims: &[usize],
176 update_strides: &[isize],
177 update_window_dims: &[usize],
178 is_update_window_dim: &[bool],
179 window_dims: &[usize],
180 dest_strides: &[isize],
181) -> (
182 AxisVec<isize>,
183 AxisVec<isize>,
184 AxisVec<isize>,
185 AxisVec<isize>,
186 isize,
187) {
188 let batch_source_strides = index_dims
189 .iter()
190 .zip(index_strides.iter())
191 .enumerate()
192 .filter_map(|(axis, (_, &stride))| (axis != index_vector_dim).then_some(stride))
193 .collect();
194 let batch_dest_strides = update_dims
195 .iter()
196 .zip(update_strides.iter())
197 .enumerate()
198 .filter_map(|(axis, (_, &stride))| (!is_update_window_dim[axis]).then_some(stride))
199 .collect();
200 let window_source_strides = update_window_dims
201 .iter()
202 .map(|&axis| update_strides[axis])
203 .collect();
204 let window_dest_strides = window_dims.iter().map(|&axis| dest_strides[axis]).collect();
205 let vector_stride = if index_vector_dim < index_dims.len() {
206 index_strides[index_vector_dim]
207 } else {
208 0
209 };
210 (
211 batch_source_strides,
212 batch_dest_strides,
213 window_source_strides,
214 window_dest_strides,
215 vector_stride,
216 )
217}
218
219impl WindowReplay {
220 fn compile(shape: &[usize], source_strides: &[isize], dest_strides: &[isize]) -> Result<Self> {
221 validate_layout_span(shape, source_strides)?;
222 validate_layout_span(shape, dest_strides)?;
223 let mut fused_shape: AxisVec<usize> = AxisVec::with_capacity(shape.len());
224 let mut axes: AxisVec<WindowReplayAxis> = AxisVec::with_capacity(shape.len());
225 for (axis, &dim) in shape.iter().enumerate() {
226 let source_step = source_strides[axis];
227 let dest_step = dest_strides[axis];
228 if let Some(previous_axis) = fused_shape.len().checked_sub(1) {
229 let previous_extent = fused_shape[previous_axis];
230 let previous_extent =
231 isize::try_from(previous_extent).map_err(|_| StridedError::OffsetOverflow)?;
232 let expected_source = axes[previous_axis]
233 .source_step
234 .checked_mul(previous_extent)
235 .ok_or(StridedError::OffsetOverflow)?;
236 let expected_dest = axes[previous_axis]
237 .dest_step
238 .checked_mul(previous_extent)
239 .ok_or(StridedError::OffsetOverflow)?;
240 if source_step == expected_source && dest_step == expected_dest {
241 let fused_extent = fused_shape[previous_axis]
242 .checked_mul(dim)
243 .ok_or(StridedError::OffsetOverflow)?;
244 fused_shape[previous_axis] = fused_extent;
245 axes[previous_axis].source_reset =
246 checked_replay_reset(fused_extent, axes[previous_axis].source_step)?;
247 axes[previous_axis].dest_reset =
248 checked_replay_reset(fused_extent, axes[previous_axis].dest_step)?;
249 continue;
250 }
251 }
252 fused_shape.push(dim);
253 axes.push(WindowReplayAxis {
254 source_step,
255 source_reset: checked_replay_reset(dim, source_step)?,
256 dest_step,
257 dest_reset: checked_replay_reset(dim, dest_step)?,
258 });
259 }
260 Ok(Self {
261 shape: fused_shape,
262 axes,
263 })
264 }
265
266 fn decode(
267 &self,
268 mut linear: usize,
269 source_base: isize,
270 dest_base: isize,
271 ) -> Result<WindowReplayState> {
272 let mut coords = CoordScratch::new(self.shape.len());
273 let mut source_offset = source_base;
274 let mut dest_offset = dest_base;
275 for (axis, (&dim, coord)) in self
276 .shape
277 .iter()
278 .zip(coords.as_mut_slice().iter_mut())
279 .enumerate()
280 {
281 *coord = linear % dim;
282 linear /= dim;
283 let replay_axis = self.axes[axis];
284 source_offset = checked_offset_add(source_offset, replay_axis.source_step, *coord)?;
285 dest_offset = checked_offset_add(dest_offset, replay_axis.dest_step, *coord)?;
286 }
287 Ok(WindowReplayState {
288 coords,
289 source_offset,
290 dest_offset,
291 })
292 }
293
294 #[inline]
295 fn advance(&self, state: &mut WindowReplayState) {
296 for ((coord, &dim), replay_axis) in state
297 .coords
298 .as_mut_slice()
299 .iter_mut()
300 .zip(self.shape.iter())
301 .zip(self.axes.iter())
302 {
303 let next = *coord + 1;
304 if next < dim {
305 *coord = next;
306 state.source_offset += replay_axis.source_step;
307 state.dest_offset += replay_axis.dest_step;
308 return;
309 }
310 *coord = 0;
311 state.source_offset += replay_axis.source_reset;
312 state.dest_offset += replay_axis.dest_reset;
313 }
314 }
315}
316
317#[derive(Clone, Debug, Eq, PartialEq)]
323pub struct ScatterSpec {
324 pub update_window_dims: Vec<usize>,
325 pub inserted_window_dims: Vec<usize>,
326 pub scatter_dims_to_operand_dims: Vec<usize>,
327 pub index_vector_dim: usize,
328}
329
330#[derive(Clone, Debug)]
332pub struct DynamicSlicePlan {
333 operand_dims: AxisVec<usize>,
334 operand_strides: AxisVec<isize>,
335 start_dims: AxisVec<usize>,
336 start_strides: AxisVec<isize>,
337 dest_dims: AxisVec<usize>,
338 dest_strides: AxisVec<isize>,
339 slice_sizes: AxisVec<usize>,
340 total: usize,
341 window: Option<FusedPairLayout>,
346 replay: WindowReplay,
347}
348
349#[derive(Clone, Debug)]
355pub struct DynamicUpdateSlicePlan {
356 operand_dims: AxisVec<usize>,
357 operand_strides: AxisVec<isize>,
358 start_dims: AxisVec<usize>,
359 start_strides: AxisVec<isize>,
360 update_dims: AxisVec<usize>,
361 update_strides: AxisVec<isize>,
362 dest_dims: AxisVec<usize>,
363 dest_strides: AxisVec<isize>,
364 total: usize,
365 copy_plan: CopyPlan,
366 window: Option<FusedPairLayout>,
371 replay: WindowReplay,
372}
373
374#[derive(Clone, Debug)]
380pub struct ScatterPlan {
381 operand_dims: AxisVec<usize>,
382 operand_strides: AxisVec<isize>,
383 index_dims: AxisVec<usize>,
384 index_strides: AxisVec<isize>,
385 update_dims: AxisVec<usize>,
386 update_strides: AxisVec<isize>,
387 dest_dims: AxisVec<usize>,
388 dest_strides: AxisVec<isize>,
389 spec: ScatterSpec,
390 window_shape: AxisVec<usize>,
391 batch_elems: usize,
392 window_elems: usize,
393 copy_plan: CopyPlan,
394 replay: ScatterReplay,
395}
396
397impl GatherPlan {
398 pub fn compile(
400 operand_dims: &[usize],
401 operand_strides: &[isize],
402 index_dims: &[usize],
403 index_strides: &[isize],
404 dest_dims: &[usize],
405 dest_strides: &[isize],
406 spec: GatherSpec,
407 ) -> Result<Self> {
408 if operand_dims.len() != operand_strides.len()
409 || index_dims.len() != index_strides.len()
410 || dest_dims.len() != dest_strides.len()
411 {
412 return Err(StridedError::StrideLengthMismatch);
413 }
414 checked_total_len(operand_dims)?;
415 checked_total_len(index_dims)?;
416 let total = checked_total_len(dest_dims)?;
417 validate_layout_span(operand_dims, operand_strides)?;
418 validate_layout_span(index_dims, index_strides)?;
419 validate_layout_span(dest_dims, dest_strides)?;
420 if !crate::layout_check::is_injective_layout(dest_dims, dest_strides) {
421 return Err(StridedError::NonInjectiveOutputLayout);
422 }
423
424 let operand_rank = operand_dims.len();
425 if spec.slice_sizes.len() != operand_rank {
426 return Err(StridedError::RankMismatch(
427 spec.slice_sizes.len(),
428 operand_rank,
429 ));
430 }
431 validate_unique_axes(&spec.collapsed_slice_dims, operand_rank)?;
432 validate_unique_axes(&spec.start_index_map, operand_rank)?;
433 if spec.index_vector_dim > index_dims.len() {
434 return Err(StridedError::InvalidAxis {
435 axis: spec.index_vector_dim,
436 rank: index_dims.len() + 1,
437 });
438 }
439
440 for (axis, (&window, &dim)) in spec.slice_sizes.iter().zip(operand_dims.iter()).enumerate()
441 {
442 if window > dim {
443 return Err(StridedError::InvalidAxis {
444 axis,
445 rank: operand_rank,
446 });
447 }
448 }
449 for &axis in &spec.collapsed_slice_dims {
450 if spec.slice_sizes[axis] != 1 {
451 return Err(StridedError::InvalidAxis {
452 axis,
453 rank: operand_rank,
454 });
455 }
456 }
457
458 let index_vector_size = if spec.index_vector_dim == index_dims.len() {
459 1
460 } else {
461 index_dims[spec.index_vector_dim]
462 };
463 if index_vector_size != spec.start_index_map.len() {
464 return Err(StridedError::RankMismatch(
465 index_vector_size,
466 spec.start_index_map.len(),
467 ));
468 }
469
470 let window_dims = operand_window_dims(operand_rank, &spec.collapsed_slice_dims);
471 if spec.offset_dims.len() != window_dims.len() {
472 return Err(StridedError::RankMismatch(
473 spec.offset_dims.len(),
474 window_dims.len(),
475 ));
476 }
477
478 let batch_shape = index_batch_shape(index_dims, spec.index_vector_dim);
479 let out_rank = batch_shape.len() + spec.offset_dims.len();
480 validate_unique_axes(&spec.offset_dims, out_rank)?;
481
482 let mut out_axis_to_operand_dim: AxisVec<Option<usize>> =
483 (0..out_rank).map(|_| None).collect();
484 for (offset_axis, &out_axis) in spec.offset_dims.iter().enumerate() {
485 out_axis_to_operand_dim[out_axis] = Some(window_dims[offset_axis]);
486 }
487
488 let mut expected_dest_dims: AxisVec<usize> = AxisVec::with_capacity(out_rank);
489 let mut batch_axis = 0usize;
490 for &operand_dim in &out_axis_to_operand_dim {
491 match operand_dim {
492 Some(axis) => expected_dest_dims.push(spec.slice_sizes[axis]),
493 None => {
494 expected_dest_dims.push(batch_shape[batch_axis]);
495 batch_axis += 1;
496 }
497 }
498 }
499 if dest_dims != &expected_dest_dims[..] {
500 return Err(StridedError::ShapeMismatch(
501 dest_dims.to_vec(),
502 expected_dest_dims.to_vec(),
503 ));
504 }
505
506 let batch_index_strides: AxisVec<isize> = index_dims
507 .iter()
508 .zip(index_strides.iter())
509 .enumerate()
510 .filter_map(|(axis, (_, &stride))| (axis != spec.index_vector_dim).then_some(stride))
511 .collect();
512 let mut batch_axis = 0usize;
513 let mut replay_axes = AxisVec::with_capacity(out_rank);
514 for (out_axis, &operand_dim) in out_axis_to_operand_dim.iter().enumerate() {
515 let (window_step, index_batch_step) = match operand_dim {
516 Some(axis) => (operand_strides[axis], 0),
517 None => {
518 let step = batch_index_strides[batch_axis];
519 batch_axis += 1;
520 (0, step)
521 }
522 };
523 replay_axes.push(GatherReplayAxis {
524 dest_step: dest_strides[out_axis],
525 dest_reset: checked_replay_reset(dest_dims[out_axis], dest_strides[out_axis])?,
526 window_step,
527 window_reset: checked_replay_reset(dest_dims[out_axis], window_step)?,
528 index_batch_step,
529 index_batch_reset: checked_replay_reset(dest_dims[out_axis], index_batch_step)?,
530 });
531 }
532
533 let vector_stride = if spec.index_vector_dim < index_dims.len() {
534 index_strides[spec.index_vector_dim]
535 } else {
536 0
537 };
538 let mut index_component_offsets = AxisVec::with_capacity(spec.start_index_map.len());
539 for component in 0..spec.start_index_map.len() {
540 index_component_offsets.push(checked_offset_add(0, vector_stride, component)?);
541 }
542
543 let index_operand_strides: AxisVec<isize> = spec
544 .start_index_map
545 .iter()
546 .map(|&axis| operand_strides[axis])
547 .collect();
548
549 Ok(Self {
550 operand_dims: operand_dims.into(),
551 operand_strides: operand_strides.into(),
552 index_dims: index_dims.into(),
553 index_strides: index_strides.into(),
554 dest_dims: dest_dims.into(),
555 dest_strides: dest_strides.into(),
556 spec,
557 replay: GatherReplay {
558 axes: replay_axes,
559 index_component_offsets,
560 index_operand_strides,
561 },
562 total,
563 })
564 }
565
566 #[inline]
567 pub fn spec(&self) -> &GatherSpec {
568 &self.spec
569 }
570
571 #[inline]
572 pub fn dest_dims(&self) -> &[usize] {
573 &self.dest_dims
574 }
575
576 pub fn execute<T, I>(
578 &self,
579 dest: &mut RawStridedMut<'_, T>,
580 operand: &RawStridedRef<'_, T>,
581 start_indices: &RawStridedRef<'_, I>,
582 exec: Exec<'_>,
583 ) -> Result<()>
584 where
585 T: Copy + Send + Sync,
586 I: GatherIndex,
587 {
588 self.execute_with_writer(dest, operand, start_indices, exec)
589 }
590
591 pub(crate) fn execute_uninit<T, I>(
594 &self,
595 dest: &mut RawStridedMut<'_, MaybeUninit<T>>,
596 operand: &RawStridedRef<'_, T>,
597 start_indices: &RawStridedRef<'_, I>,
598 exec: Exec<'_>,
599 ) -> Result<()>
600 where
601 T: Copy + Send + Sync,
602 I: GatherIndex,
603 {
604 self.execute_with_writer(dest, operand, start_indices, exec)
605 }
606
607 fn execute_with_writer<T, I, W>(
608 &self,
609 dest: &mut W,
610 operand: &RawStridedRef<'_, T>,
611 start_indices: &RawStridedRef<'_, I>,
612 exec: Exec<'_>,
613 ) -> Result<()>
614 where
615 T: Copy + Send + Sync,
616 I: GatherIndex,
617 W: OverwriteWriter<T>,
618 {
619 self.check_call(dest, operand, start_indices)?;
620 if self.total == 0 {
621 return Ok(());
622 }
623 if self.uses_rank_one_scalar_take_path() {
624 let exec = crate::threading::for_len(exec, self.total);
625 if !exec.is_serial() {
626 return self.execute_rank_one_scalar_take_parallel(
627 dest,
628 operand,
629 start_indices,
630 exec,
631 );
632 }
633 return self.execute_rank_one_scalar_take(dest, operand, start_indices);
634 }
635 let exec = crate::threading::for_len(exec, self.total);
636 if !exec.is_serial() {
637 return self.execute_parallel(dest, operand, start_indices, exec);
638 }
639
640 let mut state =
641 self.decode_replay_state(0, dest.offset(), operand.offset(), start_indices.offset())?;
642 let operand_data = operand.data();
643 let index_data = start_indices.data();
644
645 for _ in 0..self.total {
649 let mut source_offset = state.window_offset;
654 for ((&index_component_offset, &operand_stride), &operand_dim) in self
655 .replay
656 .index_component_offsets
657 .iter()
658 .zip(self.replay.index_operand_strides.iter())
659 .zip(self.spec.start_index_map.iter())
660 {
661 let index_offset = state.index_batch_offset + index_component_offset;
662 let start = unsafe { *index_data.as_ptr().offset(index_offset) }.to_i64();
665 let clamped = self.clamp_window_start(start, operand_dim);
666 source_offset += operand_stride * clamped as isize;
667 }
668
669 let value = unsafe { *operand_data.as_ptr().offset(source_offset) };
672 unsafe { dest.write_at(state.dest_offset, value) };
673 self.advance_replay_state(&mut state);
674 }
675 Ok(())
676 }
677
678 fn uses_rank_one_scalar_take_path(&self) -> bool {
679 self.operand_dims.len() == 1
680 && self.index_dims.len() == 1
681 && self.dest_dims.len() == 1
682 && self.operand_strides[0] == 1
683 && self.index_strides[0] == 1
684 && self.dest_strides[0] == 1
685 && self.spec.offset_dims.is_empty()
686 && self.spec.collapsed_slice_dims.as_slice() == [0]
687 && self.spec.start_index_map.as_slice() == [0]
688 && self.spec.index_vector_dim == 1
689 && self.spec.slice_sizes.as_slice() == [1]
690 }
691
692 fn execute_rank_one_scalar_take<T, I, W>(
693 &self,
694 dest: &mut W,
695 operand: &RawStridedRef<'_, T>,
696 start_indices: &RawStridedRef<'_, I>,
697 ) -> Result<()>
698 where
699 T: Copy,
700 I: GatherIndex,
701 W: OverwriteWriter<T>,
702 {
703 let mut dest_offset = dest.offset();
704 let mut index_offset = start_indices.offset();
705 let operand_offset = operand.offset();
706 let operand_data = operand.data();
707 let index_data = start_indices.data();
708
709 for _ in 0..self.total {
713 unsafe {
715 let start = (*index_data.as_ptr().offset(index_offset)).to_i64();
716 let source_offset = operand_offset + self.clamp_window_start(start, 0) as isize;
717 dest.write_at(dest_offset, *operand_data.as_ptr().offset(source_offset));
718 }
719 dest_offset += 1;
720 index_offset += 1;
721 }
722 Ok(())
723 }
724
725 fn execute_rank_one_scalar_take_parallel<T, I, W>(
726 &self,
727 dest: &mut W,
728 operand: &RawStridedRef<'_, T>,
729 start_indices: &RawStridedRef<'_, I>,
730 exec: Exec<'_>,
731 ) -> Result<()>
732 where
733 T: Copy + Send + Sync,
734 I: GatherIndex,
735 W: OverwriteWriter<T>,
736 {
737 let dest_offset = dest.offset();
738 let operand_offset = operand.offset();
739 let index_offset = start_indices.offset();
740 let dest_ptr = crate::threading::SendPtr(unsafe { dest.data_ptr() });
742 let operand_ptr = crate::threading::SendPtr(operand.data().as_ptr() as *mut T);
743 let index_ptr = crate::threading::SendPtr(start_indices.data().as_ptr() as *mut I);
744
745 crate::threading::parallel_map_reduce(
746 0..self.total,
747 exec,
748 &|range| {
749 let dest_ptr = dest_ptr.as_ptr();
750 let operand_ptr = operand_ptr.as_const();
751 let index_ptr = index_ptr.as_const();
752 for position in range {
755 let position = position as isize;
756 unsafe {
759 let start = (*index_ptr.offset(index_offset + position)).to_i64();
760 let source_offset =
761 operand_offset + self.clamp_window_start(start, 0) as isize;
762 dest_ptr
763 .offset(dest_offset + position)
764 .write(operand_ptr.offset(source_offset).read());
765 }
766 }
767 Ok(())
768 },
769 &|left, right| left.and(right),
770 )
771 }
772
773 fn execute_parallel<T, I, W>(
774 &self,
775 dest: &mut W,
776 operand: &RawStridedRef<'_, T>,
777 start_indices: &RawStridedRef<'_, I>,
778 exec: Exec<'_>,
779 ) -> Result<()>
780 where
781 T: Copy + Send + Sync,
782 I: GatherIndex,
783 W: OverwriteWriter<T>,
784 {
785 let dest_offset_base = dest.offset();
786 let operand_offset_base = operand.offset();
787 let index_offset_base = start_indices.offset();
788 let dest_ptr = crate::threading::SendPtr(unsafe { dest.data_ptr() });
790 let operand_ptr = crate::threading::SendPtr(operand.data().as_ptr() as *mut T);
791 let index_ptr = crate::threading::SendPtr(start_indices.data().as_ptr() as *mut I);
792
793 crate::threading::parallel_map_reduce(
794 0..self.total,
795 exec,
796 &|range| {
797 let mut state = self.decode_replay_state(
798 range.start,
799 dest_offset_base,
800 operand_offset_base,
801 index_offset_base,
802 )?;
803 let dest_ptr = dest_ptr.as_ptr();
804 let operand_ptr = operand_ptr.as_const();
805 let index_ptr = index_ptr.as_const();
806
807 for _ in range {
812 let mut source_offset = state.window_offset;
816 for ((&index_component_offset, &operand_stride), &operand_dim) in self
817 .replay
818 .index_component_offsets
819 .iter()
820 .zip(self.replay.index_operand_strides.iter())
821 .zip(self.spec.start_index_map.iter())
822 {
823 let index_offset = state.index_batch_offset + index_component_offset;
824 let start = unsafe { (*index_ptr.offset(index_offset)).to_i64() };
827 let clamped = self.clamp_window_start(start, operand_dim);
828 source_offset += operand_stride * clamped as isize;
829 }
830
831 unsafe {
834 dest_ptr
835 .offset(state.dest_offset)
836 .write(operand_ptr.offset(source_offset).read());
837 }
838 self.advance_replay_state(&mut state);
839 }
840 Ok(())
841 },
842 &|left, right| left.and(right),
843 )
844 }
845
846 fn check_call<T, I, W>(
847 &self,
848 dest: &W,
849 operand: &RawStridedRef<'_, T>,
850 start_indices: &RawStridedRef<'_, I>,
851 ) -> Result<()>
852 where
853 W: OverwriteWriter<T>,
854 {
855 if dest.dims() != &self.dest_dims[..]
856 || dest.strides() != &self.dest_strides[..]
857 || operand.dims() != &self.operand_dims[..]
858 || operand.strides() != &self.operand_strides[..]
859 || start_indices.dims() != &self.index_dims[..]
860 || start_indices.strides() != &self.index_strides[..]
861 {
862 return Err(StridedError::PlanLayoutMismatch);
863 }
864 Ok(())
865 }
866
867 fn decode_replay_state(
868 &self,
869 mut linear: usize,
870 dest_offset_base: isize,
871 operand_offset_base: isize,
872 index_offset_base: isize,
873 ) -> Result<GatherReplayState> {
874 let mut coords = AxisVec::with_capacity(self.dest_dims.len());
875 let mut dest_offset = dest_offset_base;
876 let mut window_offset = operand_offset_base;
877 let mut index_batch_offset = index_offset_base;
878 for (axis, &dim) in self.dest_dims.iter().enumerate() {
879 let coord = linear % dim;
880 linear /= dim;
881 let replay_axis = self.replay.axes[axis];
882 coords.push(coord);
883 dest_offset = checked_offset_add(dest_offset, replay_axis.dest_step, coord)?;
884 window_offset = checked_offset_add(window_offset, replay_axis.window_step, coord)?;
885 index_batch_offset =
886 checked_offset_add(index_batch_offset, replay_axis.index_batch_step, coord)?;
887 }
888 Ok(GatherReplayState {
889 coords,
890 dest_offset,
891 window_offset,
892 index_batch_offset,
893 })
894 }
895
896 #[inline]
897 fn advance_replay_state(&self, state: &mut GatherReplayState) {
898 for (axis, replay_axis) in self.replay.axes.iter().enumerate() {
899 let coord = state.coords[axis];
900 if coord + 1 < self.dest_dims[axis] {
901 state.coords[axis] = coord + 1;
902 state.dest_offset += replay_axis.dest_step;
903 state.window_offset += replay_axis.window_step;
904 state.index_batch_offset += replay_axis.index_batch_step;
905 return;
906 }
907 state.coords[axis] = 0;
908 state.dest_offset += replay_axis.dest_reset;
909 state.window_offset += replay_axis.window_reset;
910 state.index_batch_offset += replay_axis.index_batch_reset;
911 }
912 }
913
914 #[inline]
915 fn clamp_window_start(&self, start: i64, operand_dim: usize) -> usize {
916 let dim_size = self.operand_dims[operand_dim];
917 let window_size = self.spec.slice_sizes[operand_dim];
918 let max_start = dim_size.saturating_sub(window_size) as i64;
919 start.clamp(0, max_start) as usize
920 }
921}
922
923impl DynamicSlicePlan {
924 pub fn compile(
929 operand_dims: &[usize],
930 operand_strides: &[isize],
931 start_dims: &[usize],
932 start_strides: &[isize],
933 dest_dims: &[usize],
934 dest_strides: &[isize],
935 slice_sizes: &[usize],
936 ) -> Result<Self> {
937 if operand_dims.len() != operand_strides.len()
938 || start_dims.len() != start_strides.len()
939 || dest_dims.len() != dest_strides.len()
940 {
941 return Err(StridedError::StrideLengthMismatch);
942 }
943 if slice_sizes.len() != operand_dims.len() {
944 return Err(StridedError::RankMismatch(
945 slice_sizes.len(),
946 operand_dims.len(),
947 ));
948 }
949 validate_start_vector(start_dims, operand_dims.len())?;
950 checked_total_len(operand_dims)?;
951 checked_total_len(start_dims)?;
952 let total = checked_total_len(dest_dims)?;
953 validate_layout_span(operand_dims, operand_strides)?;
954 validate_layout_span(start_dims, start_strides)?;
955 validate_layout_span(dest_dims, dest_strides)?;
956 if dest_dims != slice_sizes {
957 return Err(StridedError::ShapeMismatch(
958 dest_dims.to_vec(),
959 slice_sizes.to_vec(),
960 ));
961 }
962 if !crate::layout_check::is_injective_layout(dest_dims, dest_strides) {
963 return Err(StridedError::NonInjectiveOutputLayout);
964 }
965 validate_window_sizes(operand_dims, slice_sizes)?;
966 let replay = WindowReplay::compile(slice_sizes, operand_strides, dest_strides)?;
967
968 Ok(Self {
969 operand_dims: operand_dims.into(),
970 operand_strides: operand_strides.into(),
971 start_dims: start_dims.into(),
972 start_strides: start_strides.into(),
973 dest_dims: dest_dims.into(),
974 dest_strides: dest_strides.into(),
975 slice_sizes: slice_sizes.into(),
976 total,
977 window: fuse_pair_layout(slice_sizes, dest_strides, operand_strides),
978 replay,
979 })
980 }
981
982 pub fn execute<T, I>(
984 &self,
985 dest: &mut RawStridedMut<'_, T>,
986 operand: &RawStridedRef<'_, T>,
987 starts: &RawStridedRef<'_, I>,
988 exec: Exec<'_>,
989 ) -> Result<()>
990 where
991 T: Copy + Send + Sync,
992 I: GatherIndex,
993 {
994 self.execute_with_writer(dest, operand, starts, exec)
995 }
996
997 pub(crate) fn execute_uninit<T, I>(
998 &self,
999 dest: &mut RawStridedMut<'_, MaybeUninit<T>>,
1000 operand: &RawStridedRef<'_, T>,
1001 starts: &RawStridedRef<'_, I>,
1002 exec: Exec<'_>,
1003 ) -> Result<()>
1004 where
1005 T: Copy + Send + Sync,
1006 I: GatherIndex,
1007 {
1008 self.execute_with_writer(dest, operand, starts, exec)
1009 }
1010
1011 fn execute_with_writer<T, I, W>(
1012 &self,
1013 dest: &mut W,
1014 operand: &RawStridedRef<'_, T>,
1015 starts: &RawStridedRef<'_, I>,
1016 exec: Exec<'_>,
1017 ) -> Result<()>
1018 where
1019 T: Copy + Send + Sync,
1020 I: GatherIndex,
1021 W: OverwriteWriter<T>,
1022 {
1023 self.check_call(dest, operand, starts)?;
1024 if self.total == 0 {
1025 return Ok(());
1026 }
1027 if self.uses_rank_one_contiguous_path() {
1028 return self.execute_rank_one_contiguous(dest, operand, starts, exec);
1029 }
1030 if let Some(window) = &self.window {
1031 return self.execute_fused_window(dest, operand, starts, window, exec);
1032 }
1033 let replay = &self.replay;
1034 let exec = crate::threading::for_len(exec, self.total);
1035 if !exec.is_serial() {
1036 return self.execute_parallel(dest, operand, starts, exec, replay);
1037 }
1038
1039 let mut starts_storage = CoordScratch::new(self.operand_dims.len());
1040 let clamped_starts = starts_storage.as_mut_slice();
1041 read_clamped_starts(
1042 starts,
1043 &self.operand_dims,
1044 &self.slice_sizes,
1045 clamped_starts,
1046 )?;
1047 let source_base =
1048 checked_strided_offset(operand.offset(), &self.operand_strides, clamped_starts)?;
1049 let mut state = replay.decode(0, source_base, dest.offset())?;
1050 let operand_data = operand.data();
1051
1052 for _ in 0..self.total {
1056 let value = unsafe { *operand_data.as_ptr().offset(state.source_offset) };
1058 unsafe { dest.write_at(state.dest_offset, value) };
1061 replay.advance(&mut state);
1062 }
1063 Ok(())
1064 }
1065
1066 fn execute_fused_window<T, I, W>(
1072 &self,
1073 dest: &mut W,
1074 operand: &RawStridedRef<'_, T>,
1075 starts: &RawStridedRef<'_, I>,
1076 window: &FusedPairLayout,
1077 exec: Exec<'_>,
1078 ) -> Result<()>
1079 where
1080 T: Copy + Send + Sync,
1081 I: GatherIndex,
1082 W: OverwriteWriter<T>,
1083 {
1084 let mut starts_storage = CoordScratch::new(self.operand_dims.len());
1085 let clamped_starts = starts_storage.as_mut_slice();
1086 read_clamped_starts(
1087 starts,
1088 &self.operand_dims,
1089 &self.slice_sizes,
1090 clamped_starts,
1091 )?;
1092 let source_base =
1093 checked_strided_offset(operand.offset(), &self.operand_strides, clamped_starts)?;
1094 let dest_base = dest.offset();
1095 let dest_ptr = unsafe { dest.data_ptr() }.cast::<MaybeUninit<T>>();
1097 unsafe {
1107 replay_fused_raw(
1108 dest_ptr,
1109 dest_base,
1110 operand.data().as_ptr(),
1111 source_base,
1112 window,
1113 |slot: &mut MaybeUninit<T>, value: T| *slot = MaybeUninit::new(value),
1114 |value: T| value,
1115 exec,
1116 );
1117 }
1118 Ok(())
1119 }
1120
1121 #[inline]
1122 fn uses_rank_one_contiguous_path(&self) -> bool {
1123 self.operand_dims.len() == 1 && self.operand_strides[0] == 1 && self.dest_strides[0] == 1
1124 }
1125
1126 fn execute_rank_one_contiguous<T, I, W>(
1127 &self,
1128 dest: &mut W,
1129 operand: &RawStridedRef<'_, T>,
1130 starts: &RawStridedRef<'_, I>,
1131 exec: Exec<'_>,
1132 ) -> Result<()>
1133 where
1134 T: Copy + Send + Sync,
1135 I: GatherIndex,
1136 W: OverwriteWriter<T>,
1137 {
1138 let mut clamped_starts = [0usize; 1];
1139 read_clamped_starts(
1140 starts,
1141 &self.operand_dims,
1142 &self.slice_sizes,
1143 &mut clamped_starts,
1144 )?;
1145 let source_start = checked_offset_add(operand.offset(), 1, clamped_starts[0])?;
1146 let source_start =
1147 usize::try_from(source_start).map_err(|_| StridedError::OffsetOverflow)?;
1148 let dest_start =
1149 usize::try_from(dest.offset()).map_err(|_| StridedError::OffsetOverflow)?;
1150 let source_end = source_start
1151 .checked_add(self.total)
1152 .ok_or(StridedError::OffsetOverflow)?;
1153 let source = operand
1154 .data()
1155 .get(source_start..source_end)
1156 .ok_or(StridedError::OffsetOverflow)?;
1157 let dest_ptr = unsafe { dest.data_ptr() };
1159 unsafe {
1162 crate::threading::copy_contiguous(
1165 source.as_ptr(),
1166 dest_ptr.add(dest_start),
1167 self.total,
1168 exec,
1169 );
1170 }
1171 Ok(())
1172 }
1173
1174 fn execute_parallel<T, I, W>(
1175 &self,
1176 dest: &mut W,
1177 operand: &RawStridedRef<'_, T>,
1178 starts: &RawStridedRef<'_, I>,
1179 exec: Exec<'_>,
1180 replay: &WindowReplay,
1181 ) -> Result<()>
1182 where
1183 T: Copy + Send + Sync,
1184 I: GatherIndex,
1185 W: OverwriteWriter<T>,
1186 {
1187 let mut clamped_starts: AxisVec<usize> = (0..self.operand_dims.len()).map(|_| 0).collect();
1188 read_clamped_starts(
1189 starts,
1190 &self.operand_dims,
1191 &self.slice_sizes,
1192 &mut clamped_starts,
1193 )?;
1194 let source_base =
1195 checked_strided_offset(operand.offset(), &self.operand_strides, &clamped_starts)?;
1196 let dest_base = dest.offset();
1197 let operand_ptr = crate::threading::SendPtr(operand.data().as_ptr() as *mut T);
1198 let dest_ptr = crate::threading::SendPtr(unsafe { dest.data_ptr() });
1200
1201 crate::threading::parallel_map_reduce(
1202 0..self.total,
1203 exec,
1204 &|range| {
1205 let mut state = replay.decode(range.start, source_base, dest_base)?;
1206 let operand_ptr = operand_ptr.as_const();
1207 let dest_ptr = dest_ptr.as_ptr();
1208
1209 for _ in range {
1213 unsafe {
1216 dest_ptr
1217 .offset(state.dest_offset)
1218 .write(operand_ptr.offset(state.source_offset).read());
1219 }
1220 replay.advance(&mut state);
1221 }
1222 Ok(())
1223 },
1224 &|left, right| left.and(right),
1225 )
1226 }
1227
1228 fn check_call<T, I, W>(
1229 &self,
1230 dest: &W,
1231 operand: &RawStridedRef<'_, T>,
1232 starts: &RawStridedRef<'_, I>,
1233 ) -> Result<()>
1234 where
1235 W: OverwriteWriter<T>,
1236 {
1237 if dest.dims() != &self.dest_dims[..]
1238 || dest.strides() != &self.dest_strides[..]
1239 || operand.dims() != &self.operand_dims[..]
1240 || operand.strides() != &self.operand_strides[..]
1241 || starts.dims() != &self.start_dims[..]
1242 || starts.strides() != &self.start_strides[..]
1243 {
1244 return Err(StridedError::PlanLayoutMismatch);
1245 }
1246 Ok(())
1247 }
1248}
1249
1250impl DynamicUpdateSlicePlan {
1251 #[allow(clippy::too_many_arguments)]
1256 pub fn compile(
1257 operand_dims: &[usize],
1258 operand_strides: &[isize],
1259 start_dims: &[usize],
1260 start_strides: &[isize],
1261 update_dims: &[usize],
1262 update_strides: &[isize],
1263 dest_dims: &[usize],
1264 dest_strides: &[isize],
1265 ) -> Result<Self> {
1266 if operand_dims.len() != operand_strides.len()
1267 || start_dims.len() != start_strides.len()
1268 || update_dims.len() != update_strides.len()
1269 || dest_dims.len() != dest_strides.len()
1270 {
1271 return Err(StridedError::StrideLengthMismatch);
1272 }
1273 if update_dims.len() != operand_dims.len() {
1274 return Err(StridedError::RankMismatch(
1275 update_dims.len(),
1276 operand_dims.len(),
1277 ));
1278 }
1279 validate_start_vector(start_dims, operand_dims.len())?;
1280 checked_total_len(operand_dims)?;
1281 checked_total_len(start_dims)?;
1282 let total = checked_total_len(update_dims)?;
1283 validate_layout_span(operand_dims, operand_strides)?;
1284 validate_layout_span(start_dims, start_strides)?;
1285 validate_layout_span(update_dims, update_strides)?;
1286 validate_layout_span(dest_dims, dest_strides)?;
1287 if dest_dims != operand_dims {
1288 return Err(StridedError::ShapeMismatch(
1289 dest_dims.to_vec(),
1290 operand_dims.to_vec(),
1291 ));
1292 }
1293 if !crate::layout_check::is_injective_layout(dest_dims, dest_strides) {
1294 return Err(StridedError::NonInjectiveOutputLayout);
1295 }
1296 validate_window_sizes(operand_dims, update_dims)?;
1297 let copy_plan = CopyPlan::compile(operand_dims, dest_strides, operand_strides)?;
1298 let replay = WindowReplay::compile(update_dims, update_strides, dest_strides)?;
1299
1300 Ok(Self {
1301 operand_dims: operand_dims.into(),
1302 operand_strides: operand_strides.into(),
1303 start_dims: start_dims.into(),
1304 start_strides: start_strides.into(),
1305 update_dims: update_dims.into(),
1306 update_strides: update_strides.into(),
1307 dest_dims: dest_dims.into(),
1308 dest_strides: dest_strides.into(),
1309 total,
1310 copy_plan,
1311 window: fuse_pair_layout(update_dims, dest_strides, update_strides),
1312 replay,
1313 })
1314 }
1315
1316 pub fn execute<T, I>(
1318 &self,
1319 dest: &mut RawStridedMut<'_, T>,
1320 operand: &RawStridedRef<'_, T>,
1321 update: &RawStridedRef<'_, T>,
1322 starts: &RawStridedRef<'_, I>,
1323 exec: Exec<'_>,
1324 ) -> Result<()>
1325 where
1326 T: Copy + Send + Sync,
1327 I: GatherIndex,
1328 {
1329 let mut exec = exec;
1330 self.check_call(dest, operand, update, starts)?;
1331 self.copy_plan.execute(dest, operand, exec.reborrow())?;
1332 self.execute_update_with_writer(dest, update, starts, exec)
1333 }
1334
1335 pub(crate) fn execute_uninit<'a, T, I>(
1338 &self,
1339 dest: &'a mut RawStridedMut<'a, MaybeUninit<T>>,
1340 operand: &RawStridedRef<'_, T>,
1341 update: &RawStridedRef<'_, T>,
1342 starts: &RawStridedRef<'_, I>,
1343 exec: Exec<'_>,
1344 ) -> Result<()>
1345 where
1346 T: Copy + Send + Sync,
1347 I: GatherIndex,
1348 {
1349 self.check_call(dest, operand, update, starts)?;
1350 self.copy_plan
1351 .execute_uninit_then(dest, operand, exec, |mut receipt, exec| {
1352 self.execute_update_with_writer(&mut receipt, update, starts, exec)
1353 })?
1354 }
1355
1356 fn execute_update_with_writer<T, I, W>(
1357 &self,
1358 dest: &mut W,
1359 update: &RawStridedRef<'_, T>,
1360 starts: &RawStridedRef<'_, I>,
1361 exec: Exec<'_>,
1362 ) -> Result<()>
1363 where
1364 T: Copy + Send + Sync,
1365 I: GatherIndex,
1366 W: OverwriteWriter<T>,
1367 {
1368 if self.total == 0 {
1369 return Ok(());
1370 }
1371 if self.uses_rank_one_contiguous_path() {
1372 return self.execute_rank_one_contiguous(dest, update, starts, exec);
1373 }
1374 if let Some(window) = &self.window {
1375 return self.execute_fused_window(dest, update, starts, window, exec);
1376 }
1377 let replay = &self.replay;
1378 let exec = crate::threading::for_len(exec, self.total);
1379 if !exec.is_serial() {
1380 return self.execute_update_parallel(dest, update, starts, exec, replay);
1381 }
1382
1383 let mut starts_storage = CoordScratch::new(self.operand_dims.len());
1384 let clamped_starts = starts_storage.as_mut_slice();
1385 read_clamped_starts(
1386 starts,
1387 &self.operand_dims,
1388 &self.update_dims,
1389 clamped_starts,
1390 )?;
1391 let source_base = update.offset();
1392 let dest_base = checked_strided_offset(dest.offset(), &self.dest_strides, &clamped_starts)?;
1393 let mut state = replay.decode(0, source_base, dest_base)?;
1394 let update_data = update.data();
1395
1396 for _ in 0..self.total {
1400 let value = unsafe { *update_data.as_ptr().offset(state.source_offset) };
1402 unsafe { dest.write_at(state.dest_offset, value) };
1405 replay.advance(&mut state);
1406 }
1407 Ok(())
1408 }
1409
1410 fn execute_fused_window<T, I, W>(
1413 &self,
1414 dest: &mut W,
1415 update: &RawStridedRef<'_, T>,
1416 starts: &RawStridedRef<'_, I>,
1417 window: &FusedPairLayout,
1418 exec: Exec<'_>,
1419 ) -> Result<()>
1420 where
1421 T: Copy + Send + Sync,
1422 I: GatherIndex,
1423 W: OverwriteWriter<T>,
1424 {
1425 let mut starts_storage = CoordScratch::new(self.operand_dims.len());
1426 let clamped_starts = starts_storage.as_mut_slice();
1427 read_clamped_starts(
1428 starts,
1429 &self.operand_dims,
1430 &self.update_dims,
1431 clamped_starts,
1432 )?;
1433 let dest_base = checked_strided_offset(dest.offset(), &self.dest_strides, clamped_starts)?;
1434 let dest_ptr = unsafe { dest.data_ptr() }.cast::<MaybeUninit<T>>();
1436 unsafe {
1444 replay_fused_raw(
1445 dest_ptr,
1446 dest_base,
1447 update.data().as_ptr(),
1448 update.offset(),
1449 window,
1450 |slot: &mut MaybeUninit<T>, value: T| *slot = MaybeUninit::new(value),
1451 |value: T| value,
1452 exec,
1453 );
1454 }
1455 Ok(())
1456 }
1457
1458 #[inline]
1459 fn uses_rank_one_contiguous_path(&self) -> bool {
1460 self.operand_dims.len() == 1
1461 && self.operand_strides[0] == 1
1462 && self.update_strides[0] == 1
1463 && self.dest_strides[0] == 1
1464 }
1465
1466 fn execute_rank_one_contiguous<T, I, W>(
1467 &self,
1468 dest: &mut W,
1469 update: &RawStridedRef<'_, T>,
1470 starts: &RawStridedRef<'_, I>,
1471 exec: Exec<'_>,
1472 ) -> Result<()>
1473 where
1474 T: Copy + Send + Sync,
1475 I: GatherIndex,
1476 W: OverwriteWriter<T>,
1477 {
1478 let mut clamped_starts = [0usize; 1];
1479 read_clamped_starts(
1480 starts,
1481 &self.operand_dims,
1482 &self.update_dims,
1483 &mut clamped_starts,
1484 )?;
1485 let update_start =
1486 usize::try_from(update.offset()).map_err(|_| StridedError::OffsetOverflow)?;
1487 let dest_start = checked_offset_add(dest.offset(), 1, clamped_starts[0])?;
1488 let dest_start = usize::try_from(dest_start).map_err(|_| StridedError::OffsetOverflow)?;
1489 let update_end = update_start
1490 .checked_add(self.total)
1491 .ok_or(StridedError::OffsetOverflow)?;
1492 let update = update
1493 .data()
1494 .get(update_start..update_end)
1495 .ok_or(StridedError::OffsetOverflow)?;
1496 let dest_ptr = unsafe { dest.data_ptr() };
1498 unsafe {
1500 crate::threading::copy_contiguous(
1503 update.as_ptr(),
1504 dest_ptr.add(dest_start),
1505 self.total,
1506 exec,
1507 );
1508 }
1509 Ok(())
1510 }
1511
1512 fn execute_update_parallel<T, I, W>(
1513 &self,
1514 dest: &mut W,
1515 update: &RawStridedRef<'_, T>,
1516 starts: &RawStridedRef<'_, I>,
1517 exec: Exec<'_>,
1518 replay: &WindowReplay,
1519 ) -> Result<()>
1520 where
1521 T: Copy + Send + Sync,
1522 I: GatherIndex,
1523 W: OverwriteWriter<T>,
1524 {
1525 let mut clamped_starts: AxisVec<usize> = (0..self.operand_dims.len()).map(|_| 0).collect();
1526 read_clamped_starts(
1527 starts,
1528 &self.operand_dims,
1529 &self.update_dims,
1530 &mut clamped_starts,
1531 )?;
1532 let source_base = update.offset();
1533 let dest_base = checked_strided_offset(dest.offset(), &self.dest_strides, &clamped_starts)?;
1534 let update_ptr = crate::threading::SendPtr(update.data().as_ptr() as *mut T);
1535 let dest_ptr = crate::threading::SendPtr(unsafe { dest.data_ptr() });
1537
1538 crate::threading::parallel_map_reduce(
1539 0..self.total,
1540 exec,
1541 &|range| {
1542 let mut state = replay.decode(range.start, source_base, dest_base)?;
1543 let update_ptr = update_ptr.as_const();
1544 let dest_ptr = dest_ptr.as_ptr();
1545
1546 for _ in range {
1550 unsafe {
1553 dest_ptr
1554 .offset(state.dest_offset)
1555 .write(update_ptr.offset(state.source_offset).read());
1556 }
1557 replay.advance(&mut state);
1558 }
1559 Ok(())
1560 },
1561 &|left, right| left.and(right),
1562 )
1563 }
1564
1565 fn check_call<T, I, W>(
1566 &self,
1567 dest: &W,
1568 operand: &RawStridedRef<'_, T>,
1569 update: &RawStridedRef<'_, T>,
1570 starts: &RawStridedRef<'_, I>,
1571 ) -> Result<()>
1572 where
1573 W: OverwriteWriter<T>,
1574 {
1575 if dest.dims() != &self.dest_dims[..]
1576 || dest.strides() != &self.dest_strides[..]
1577 || operand.dims() != &self.operand_dims[..]
1578 || operand.strides() != &self.operand_strides[..]
1579 || update.dims() != &self.update_dims[..]
1580 || update.strides() != &self.update_strides[..]
1581 || starts.dims() != &self.start_dims[..]
1582 || starts.strides() != &self.start_strides[..]
1583 {
1584 return Err(StridedError::PlanLayoutMismatch);
1585 }
1586 Ok(())
1587 }
1588}
1589
1590impl ScatterPlan {
1591 #[allow(clippy::too_many_arguments)]
1593 pub fn compile(
1594 operand_dims: &[usize],
1595 operand_strides: &[isize],
1596 index_dims: &[usize],
1597 index_strides: &[isize],
1598 update_dims: &[usize],
1599 update_strides: &[isize],
1600 dest_dims: &[usize],
1601 dest_strides: &[isize],
1602 spec: ScatterSpec,
1603 ) -> Result<Self> {
1604 if operand_dims.len() != operand_strides.len()
1605 || index_dims.len() != index_strides.len()
1606 || update_dims.len() != update_strides.len()
1607 || dest_dims.len() != dest_strides.len()
1608 {
1609 return Err(StridedError::StrideLengthMismatch);
1610 }
1611 checked_total_len(operand_dims)?;
1612 checked_total_len(index_dims)?;
1613 checked_total_len(update_dims)?;
1614 if dest_dims != operand_dims {
1615 return Err(StridedError::ShapeMismatch(
1616 dest_dims.to_vec(),
1617 operand_dims.to_vec(),
1618 ));
1619 }
1620 if !crate::layout_check::is_injective_layout(dest_dims, dest_strides) {
1621 return Err(StridedError::NonInjectiveOutputLayout);
1622 }
1623
1624 let operand_rank = operand_dims.len();
1625 validate_unique_axes(&spec.inserted_window_dims, operand_rank)?;
1626 validate_unique_axes(&spec.scatter_dims_to_operand_dims, operand_rank)?;
1627 if spec.index_vector_dim > index_dims.len() {
1628 return Err(StridedError::InvalidAxis {
1629 axis: spec.index_vector_dim,
1630 rank: index_dims.len() + 1,
1631 });
1632 }
1633 let index_vector_size = if spec.index_vector_dim == index_dims.len() {
1634 1
1635 } else {
1636 index_dims[spec.index_vector_dim]
1637 };
1638 if index_vector_size != spec.scatter_dims_to_operand_dims.len() {
1639 return Err(StridedError::RankMismatch(
1640 index_vector_size,
1641 spec.scatter_dims_to_operand_dims.len(),
1642 ));
1643 }
1644
1645 let batch_shape = index_batch_shape(index_dims, spec.index_vector_dim);
1646 let window_dims = operand_window_dims(operand_rank, &spec.inserted_window_dims);
1647 if spec.update_window_dims.len() != window_dims.len() {
1648 return Err(StridedError::RankMismatch(
1649 spec.update_window_dims.len(),
1650 window_dims.len(),
1651 ));
1652 }
1653
1654 let update_rank = update_dims.len();
1655 let expected_batch_rank = update_rank
1656 .checked_sub(spec.update_window_dims.len())
1657 .ok_or(StridedError::RankMismatch(
1658 spec.update_window_dims.len(),
1659 update_rank,
1660 ))?;
1661 if expected_batch_rank != batch_shape.len() {
1662 return Err(StridedError::RankMismatch(
1663 expected_batch_rank,
1664 batch_shape.len(),
1665 ));
1666 }
1667 validate_unique_axes(&spec.update_window_dims, update_rank)?;
1668
1669 let mut is_update_window_dim: AxisVec<bool> = (0..update_rank).map(|_| false).collect();
1670 for &axis in &spec.update_window_dims {
1671 is_update_window_dim[axis] = true;
1672 }
1673
1674 let mut batch_axis = 0usize;
1675 for axis in 0..update_rank {
1676 if !is_update_window_dim[axis] {
1677 if update_dims[axis] != batch_shape[batch_axis] {
1678 return Err(StridedError::ShapeMismatch(
1679 update_dims.to_vec(),
1680 expected_scatter_update_shape(&batch_shape, &spec, update_dims).to_vec(),
1681 ));
1682 }
1683 batch_axis += 1;
1684 }
1685 }
1686
1687 let mut window_shape: AxisVec<usize> = (0..operand_rank).map(|_| 1).collect();
1688 let mut window_shape_updates: AxisVec<usize> =
1689 AxisVec::with_capacity(spec.update_window_dims.len());
1690 for (pos, &update_axis) in spec.update_window_dims.iter().enumerate() {
1691 let dim = update_dims[update_axis];
1692 window_shape_updates.push(dim);
1693 window_shape[window_dims[pos]] = dim;
1694 }
1695 validate_window_sizes(operand_dims, &window_shape)?;
1696
1697 let batch_elems = checked_total_len(&batch_shape)?;
1698 let window_elems = checked_total_len(&window_shape_updates)?;
1699 let copy_plan = CopyPlan::compile(operand_dims, dest_strides, operand_strides)?;
1700 let replay = ScatterReplay::compile(
1701 &batch_shape,
1702 index_dims,
1703 index_strides,
1704 spec.index_vector_dim,
1705 spec.scatter_dims_to_operand_dims.len(),
1706 update_dims,
1707 update_strides,
1708 &spec.update_window_dims,
1709 &is_update_window_dim,
1710 &window_shape_updates,
1711 &window_dims,
1712 dest_strides,
1713 )?;
1714
1715 Ok(Self {
1716 operand_dims: operand_dims.into(),
1717 operand_strides: operand_strides.into(),
1718 index_dims: index_dims.into(),
1719 index_strides: index_strides.into(),
1720 update_dims: update_dims.into(),
1721 update_strides: update_strides.into(),
1722 dest_dims: dest_dims.into(),
1723 dest_strides: dest_strides.into(),
1724 spec,
1725 window_shape,
1726 batch_elems,
1727 window_elems,
1728 copy_plan,
1729 replay,
1730 })
1731 }
1732
1733 #[inline(always)]
1735 pub fn execute<T, I>(
1736 &self,
1737 dest: &mut RawStridedMut<'_, T>,
1738 operand: &RawStridedRef<'_, T>,
1739 scatter_indices: &RawStridedRef<'_, I>,
1740 updates: &RawStridedRef<'_, T>,
1741 exec: Exec<'_>,
1742 ) -> Result<()>
1743 where
1744 T: Copy + Add<Output = T> + Send + Sync,
1745 I: GatherIndex,
1746 {
1747 self.check_call(dest, operand, scatter_indices, updates)?;
1748 self.copy_plan.execute(dest, operand, exec)?;
1749 self.execute_updates(dest, scatter_indices, updates, |a, b| a + b)
1750 }
1751
1752 pub(crate) fn execute_uninit<'a, T, I>(
1755 &self,
1756 dest: &'a mut RawStridedMut<'a, MaybeUninit<T>>,
1757 operand: &RawStridedRef<'_, T>,
1758 scatter_indices: &RawStridedRef<'_, I>,
1759 updates: &RawStridedRef<'_, T>,
1760 combine: fn(T, T) -> T,
1761 exec: Exec<'_>,
1762 ) -> Result<()>
1763 where
1764 T: Copy + Add<Output = T> + Send + Sync,
1765 I: GatherIndex,
1766 {
1767 self.check_call(dest, operand, scatter_indices, updates)?;
1768 self.copy_plan
1769 .execute_uninit_then(dest, operand, exec, |mut receipt, _| {
1770 self.execute_updates(&mut receipt, scatter_indices, updates, combine)
1771 })?
1772 }
1773
1774 #[inline(always)]
1775 fn execute_updates<T, I, W, F>(
1776 &self,
1777 dest: &mut W,
1778 scatter_indices: &RawStridedRef<'_, I>,
1779 updates: &RawStridedRef<'_, T>,
1780 combine: F,
1781 ) -> Result<()>
1782 where
1783 T: Copy + Send + Sync,
1784 I: GatherIndex,
1785 W: ReadModifyWrite<T>,
1786 F: Fn(T, T) -> T + Copy,
1787 {
1788 if self.batch_elems == 0 || self.window_elems == 0 {
1789 return Ok(());
1790 }
1791 if self.uses_rank_one_scalar_update_path() {
1792 return self.execute_rank_one_scalar_updates(dest, scatter_indices, updates, combine);
1793 }
1794 self.execute_generic_updates(dest, scatter_indices, updates, combine)
1795 }
1796
1797 #[inline(never)]
1798 fn execute_generic_updates<T, I, W, F>(
1799 &self,
1800 dest: &mut W,
1801 scatter_indices: &RawStridedRef<'_, I>,
1802 updates: &RawStridedRef<'_, T>,
1803 combine: F,
1804 ) -> Result<()>
1805 where
1806 T: Copy + Send + Sync,
1807 I: GatherIndex,
1808 W: ReadModifyWrite<T>,
1809 F: Fn(T, T) -> T + Copy,
1810 {
1811 let replay = &self.replay;
1814
1815 let mut operand_base_storage = CoordScratch::new(self.operand_dims.len());
1816 let operand_base = operand_base_storage.as_mut_slice();
1817 let mut batch_state = replay
1818 .batch
1819 .decode(0, scatter_indices.offset(), updates.offset())?;
1820 let index_data = scatter_indices.data();
1821 let update_data = updates.data();
1822
1823 for _ in 0..self.batch_elems {
1826 operand_base.fill(0);
1827 for (&component_offset, &operand_axis) in replay
1828 .index_component_offsets
1829 .iter()
1830 .zip(self.spec.scatter_dims_to_operand_dims.iter())
1831 {
1832 let index_offset = batch_state
1833 .source_offset
1834 .checked_add(component_offset)
1835 .ok_or(StridedError::OffsetOverflow)?;
1836 let start = unsafe { *index_data.as_ptr().offset(index_offset) }.to_i64();
1839 operand_base[operand_axis] = clamp_window_start(
1840 start,
1841 self.operand_dims[operand_axis],
1842 self.window_shape[operand_axis],
1843 );
1844 }
1845
1846 let dest_base = checked_strided_offset(dest.offset(), dest.strides(), operand_base)?;
1847 let mut window_state = replay
1848 .window
1849 .decode(0, batch_state.dest_offset, dest_base)?;
1850 for _ in 0..self.window_elems {
1851 let value = unsafe { *update_data.as_ptr().offset(window_state.source_offset) };
1854 unsafe { dest.add_at(window_state.dest_offset, value, combine) };
1855 replay.window.advance(&mut window_state);
1856 }
1857 replay.batch.advance(&mut batch_state);
1858 }
1859 Ok(())
1860 }
1861
1862 fn uses_rank_one_scalar_update_path(&self) -> bool {
1863 self.operand_dims.len() == 1
1864 && self.index_dims.len() == 2
1865 && self.update_dims.len() == 1
1866 && self.dest_dims.len() == 1
1867 && self.operand_strides[0] == 1
1868 && self.index_strides[0] == 1
1869 && self.update_strides[0] == 1
1870 && self.dest_strides[0] == 1
1871 && self.index_dims[1] == 1
1872 && self.spec.update_window_dims.is_empty()
1873 && self.spec.inserted_window_dims.as_slice() == [0]
1874 && self.spec.scatter_dims_to_operand_dims.as_slice() == [0]
1875 && self.spec.index_vector_dim == 1
1876 && self.window_elems == 1
1877 }
1878
1879 #[inline(always)]
1880 fn execute_rank_one_scalar_updates<T, I, W, F>(
1881 &self,
1882 dest: &mut W,
1883 scatter_indices: &RawStridedRef<'_, I>,
1884 updates: &RawStridedRef<'_, T>,
1885 combine: F,
1886 ) -> Result<()>
1887 where
1888 T: Copy,
1889 I: GatherIndex,
1890 W: ReadModifyWrite<T>,
1891 F: Fn(T, T) -> T + Copy,
1892 {
1893 let mut index_offset = scatter_indices.offset();
1894 let mut update_offset = updates.offset();
1895 let dest_offset = dest.offset();
1896 let index_data = scatter_indices.data();
1897 let update_data = updates.data();
1898 let dest_ptr = unsafe { dest.data_ptr() };
1900
1901 for _ in 0..self.batch_elems {
1904 unsafe {
1907 let start = (*index_data.as_ptr().offset(index_offset)).to_i64();
1908 let output_offset =
1909 dest_offset + clamp_window_start(start, self.operand_dims[0], 1) as isize;
1910 let output = dest_ptr.offset(output_offset);
1911 output.write(combine(
1912 output.read(),
1913 *update_data.as_ptr().offset(update_offset),
1914 ));
1915 }
1916 index_offset += 1;
1917 update_offset += 1;
1918 }
1919 Ok(())
1920 }
1921
1922 fn check_call<T, I, W>(
1923 &self,
1924 dest: &W,
1925 operand: &RawStridedRef<'_, T>,
1926 scatter_indices: &RawStridedRef<'_, I>,
1927 updates: &RawStridedRef<'_, T>,
1928 ) -> Result<()>
1929 where
1930 W: OverwriteWriter<T>,
1931 {
1932 if dest.dims() != &self.dest_dims[..]
1933 || dest.strides() != &self.dest_strides[..]
1934 || operand.dims() != &self.operand_dims[..]
1935 || operand.strides() != &self.operand_strides[..]
1936 || scatter_indices.dims() != &self.index_dims[..]
1937 || scatter_indices.strides() != &self.index_strides[..]
1938 || updates.dims() != &self.update_dims[..]
1939 || updates.strides() != &self.update_strides[..]
1940 {
1941 return Err(StridedError::PlanLayoutMismatch);
1942 }
1943 Ok(())
1944 }
1945}
1946
1947fn validate_unique_axes(axes: &[usize], rank: usize) -> Result<()> {
1948 let mut seen: InlineVec<bool> = InlineVec::new();
1951 seen.resize(rank, false);
1952 for &axis in axes {
1953 if axis >= rank {
1954 return Err(StridedError::InvalidAxis { axis, rank });
1955 }
1956 if seen[axis] {
1957 return Err(StridedError::InvalidAxis { axis, rank });
1958 }
1959 seen[axis] = true;
1960 }
1961 Ok(())
1962}
1963
1964fn validate_start_vector(start_dims: &[usize], operand_rank: usize) -> Result<()> {
1965 if start_dims.len() != 1 {
1966 return Err(StridedError::RankMismatch(start_dims.len(), 1));
1967 }
1968 if start_dims[0] != operand_rank {
1969 return Err(StridedError::RankMismatch(start_dims[0], operand_rank));
1970 }
1971 Ok(())
1972}
1973
1974fn validate_window_sizes(operand_dims: &[usize], window_sizes: &[usize]) -> Result<()> {
1975 if operand_dims.len() != window_sizes.len() {
1976 return Err(StridedError::RankMismatch(
1977 window_sizes.len(),
1978 operand_dims.len(),
1979 ));
1980 }
1981 for (axis, (&window, &dim)) in window_sizes.iter().zip(operand_dims.iter()).enumerate() {
1982 if window > dim {
1983 return Err(StridedError::InvalidAxis {
1984 axis,
1985 rank: operand_dims.len(),
1986 });
1987 }
1988 }
1989 Ok(())
1990}
1991
1992fn read_clamped_starts<I>(
1993 starts: &RawStridedRef<'_, I>,
1994 operand_dims: &[usize],
1995 window_sizes: &[usize],
1996 out: &mut [usize],
1997) -> Result<()>
1998where
1999 I: GatherIndex,
2000{
2001 debug_assert_eq!(operand_dims.len(), window_sizes.len());
2002 debug_assert_eq!(operand_dims.len(), out.len());
2003 for axis in 0..operand_dims.len() {
2004 let offset = checked_offset_add(starts.offset(), starts.strides()[0], axis)?;
2005 let start = unsafe { *starts.data().as_ptr().offset(offset) }.to_i64();
2006 out[axis] = clamp_window_start(start, operand_dims[axis], window_sizes[axis]);
2007 }
2008 Ok(())
2009}
2010
2011#[inline]
2012fn clamp_window_start(start: i64, dim_size: usize, window_size: usize) -> usize {
2013 let max_start = dim_size.saturating_sub(window_size) as i64;
2014 start.clamp(0, max_start) as usize
2015}
2016
2017fn expected_scatter_update_shape(
2018 batch_shape: &[usize],
2019 spec: &ScatterSpec,
2020 update_dims: &[usize],
2021) -> AxisVec<usize> {
2022 let mut expected: AxisVec<usize> = AxisVec::with_capacity(update_dims.len());
2023 let mut batch_axis = 0usize;
2024 for axis in 0..update_dims.len() {
2025 if spec.update_window_dims.contains(&axis) {
2026 expected.push(update_dims[axis]);
2027 } else {
2028 expected.push(batch_shape[batch_axis]);
2029 batch_axis += 1;
2030 }
2031 }
2032 expected
2033}
2034
2035fn operand_window_dims(rank: usize, collapsed_slice_dims: &[usize]) -> AxisVec<usize> {
2036 (0..rank)
2037 .filter(|axis| !collapsed_slice_dims.contains(axis))
2038 .collect()
2039}
2040
2041fn index_batch_shape(index_dims: &[usize], index_vector_dim: usize) -> AxisVec<usize> {
2042 if index_vector_dim == index_dims.len() {
2043 return index_dims.into();
2044 }
2045 index_dims
2046 .iter()
2047 .enumerate()
2048 .filter_map(|(axis, &dim)| (axis != index_vector_dim).then_some(dim))
2049 .collect()
2050}
2051
2052fn validate_layout_span(dims: &[usize], strides: &[isize]) -> Result<()> {
2053 if dims.len() != strides.len() {
2054 return Err(StridedError::StrideLengthMismatch);
2055 }
2056 let mut min_offset = 0isize;
2057 let mut max_offset = 0isize;
2058 for (&dim, &stride) in dims.iter().zip(strides.iter()) {
2059 let last =
2060 isize::try_from(dim.saturating_sub(1)).map_err(|_| StridedError::OffsetOverflow)?;
2061 let extent = stride
2062 .checked_mul(last)
2063 .ok_or(StridedError::OffsetOverflow)?;
2064 if extent < 0 {
2065 min_offset = min_offset
2066 .checked_add(extent)
2067 .ok_or(StridedError::OffsetOverflow)?;
2068 } else {
2069 max_offset = max_offset
2070 .checked_add(extent)
2071 .ok_or(StridedError::OffsetOverflow)?;
2072 }
2073 }
2074 Ok(())
2075}
2076
2077fn checked_replay_reset(dim: usize, step: isize) -> Result<isize> {
2078 if dim == 0 {
2079 return Ok(0);
2080 }
2081 let last = isize::try_from(dim - 1).map_err(|_| StridedError::OffsetOverflow)?;
2082 step.checked_mul(last)
2083 .and_then(isize::checked_neg)
2084 .ok_or(StridedError::OffsetOverflow)
2085}
2086
2087fn checked_total_len(dims: &[usize]) -> Result<usize> {
2088 if dims.is_empty() {
2089 return Ok(1);
2090 }
2091 dims.iter()
2092 .try_fold(1usize, |acc, &dim| acc.checked_mul(dim))
2093 .ok_or(StridedError::OffsetOverflow)
2094}
2095
2096fn checked_strided_offset(base: isize, strides: &[isize], index: &[usize]) -> Result<isize> {
2097 let mut offset = base;
2098 for (&stride, &coord) in strides.iter().zip(index.iter()) {
2099 offset = checked_offset_add(offset, stride, coord)?;
2100 }
2101 Ok(offset)
2102}
2103
2104fn checked_offset_add(base: isize, stride: isize, coord: usize) -> Result<isize> {
2105 let coord = isize::try_from(coord).map_err(|_| StridedError::OffsetOverflow)?;
2106 let scaled = stride
2107 .checked_mul(coord)
2108 .ok_or(StridedError::OffsetOverflow)?;
2109 base.checked_add(scaled).ok_or(StridedError::OffsetOverflow)
2110}
2111
2112struct CoordScratch {
2113 inline: [usize; RAW_FUSED_RANK_LIMIT],
2114 heap: Option<Vec<usize>>,
2115 len: usize,
2116}
2117
2118impl CoordScratch {
2119 fn new(len: usize) -> Self {
2120 if len <= RAW_FUSED_RANK_LIMIT {
2121 Self {
2122 inline: [0; RAW_FUSED_RANK_LIMIT],
2123 heap: None,
2124 len,
2125 }
2126 } else {
2127 Self {
2128 inline: [0; RAW_FUSED_RANK_LIMIT],
2129 heap: Some(vec![0; len]),
2130 len,
2131 }
2132 }
2133 }
2134
2135 fn as_mut_slice(&mut self) -> &mut [usize] {
2136 match &mut self.heap {
2137 Some(heap) => heap,
2138 None => &mut self.inline[..self.len],
2139 }
2140 }
2141}
2142
2143#[cfg(test)]
2144#[path = "gather_plan/tests/tests.rs"]
2145mod tests;
2146
2147#[cfg(test)]
2148#[path = "gather_plan/tests/parallel_tests.rs"]
2149mod parallel_tests;