1use core::{mem::MaybeUninit, ops::Add};
9
10use crate::copy_plan::{CopyPlan, OverwriteWriter, ReadModifyWrite};
11use crate::{
12 MaybeSendSync, RawStridedMut, RawStridedRef, Result, StridedError, RAW_FUSED_RANK_LIMIT,
13};
14
15#[cfg(feature = "parallel")]
16type AxisVec<T> = smallvec::SmallVec<[T; RAW_FUSED_RANK_LIMIT]>;
17#[cfg(not(feature = "parallel"))]
18type AxisVec<T> = Vec<T>;
19
20#[derive(Clone, Debug, Eq, PartialEq)]
32pub struct GatherSpec {
33 pub offset_dims: Vec<usize>,
34 pub collapsed_slice_dims: Vec<usize>,
35 pub start_index_map: Vec<usize>,
36 pub index_vector_dim: usize,
37 pub slice_sizes: Vec<usize>,
38}
39
40pub trait GatherIndex: Copy + MaybeSendSync {
42 fn to_i64(self) -> i64;
43}
44
45impl GatherIndex for i32 {
46 #[inline]
47 fn to_i64(self) -> i64 {
48 i64::from(self)
49 }
50}
51
52impl GatherIndex for i64 {
53 #[inline]
54 fn to_i64(self) -> i64 {
55 self
56 }
57}
58
59#[derive(Clone, Debug)]
61pub struct GatherPlan {
62 operand_dims: AxisVec<usize>,
63 operand_strides: AxisVec<isize>,
64 index_dims: AxisVec<usize>,
65 index_strides: AxisVec<isize>,
66 dest_dims: AxisVec<usize>,
67 dest_strides: AxisVec<isize>,
68 spec: GatherSpec,
69 batch_shape: AxisVec<usize>,
70 out_axis_to_operand_dim: AxisVec<Option<usize>>,
71 total: usize,
72}
73
74#[derive(Clone, Debug, Eq, PartialEq)]
80pub struct ScatterSpec {
81 pub update_window_dims: Vec<usize>,
82 pub inserted_window_dims: Vec<usize>,
83 pub scatter_dims_to_operand_dims: Vec<usize>,
84 pub index_vector_dim: usize,
85}
86
87#[derive(Clone, Debug)]
89pub struct DynamicSlicePlan {
90 operand_dims: AxisVec<usize>,
91 operand_strides: AxisVec<isize>,
92 start_dims: AxisVec<usize>,
93 start_strides: AxisVec<isize>,
94 dest_dims: AxisVec<usize>,
95 dest_strides: AxisVec<isize>,
96 slice_sizes: AxisVec<usize>,
97 total: usize,
98}
99
100#[derive(Clone, Debug)]
106pub struct DynamicUpdateSlicePlan {
107 operand_dims: AxisVec<usize>,
108 operand_strides: AxisVec<isize>,
109 start_dims: AxisVec<usize>,
110 start_strides: AxisVec<isize>,
111 update_dims: AxisVec<usize>,
112 update_strides: AxisVec<isize>,
113 dest_dims: AxisVec<usize>,
114 dest_strides: AxisVec<isize>,
115 total: usize,
116 copy_plan: CopyPlan,
117}
118
119#[derive(Clone, Debug)]
125pub struct ScatterPlan {
126 operand_dims: AxisVec<usize>,
127 operand_strides: AxisVec<isize>,
128 index_dims: AxisVec<usize>,
129 index_strides: AxisVec<isize>,
130 update_dims: AxisVec<usize>,
131 update_strides: AxisVec<isize>,
132 dest_dims: AxisVec<usize>,
133 dest_strides: AxisVec<isize>,
134 spec: ScatterSpec,
135 batch_shape: AxisVec<usize>,
136 window_dims: AxisVec<usize>,
137 window_shape: AxisVec<usize>,
138 window_shape_updates: AxisVec<usize>,
139 is_update_window_dim: AxisVec<bool>,
140 batch_elems: usize,
141 window_elems: usize,
142 copy_plan: CopyPlan,
143}
144
145impl GatherPlan {
146 pub fn compile(
148 operand_dims: &[usize],
149 operand_strides: &[isize],
150 index_dims: &[usize],
151 index_strides: &[isize],
152 dest_dims: &[usize],
153 dest_strides: &[isize],
154 spec: GatherSpec,
155 ) -> Result<Self> {
156 if operand_dims.len() != operand_strides.len()
157 || index_dims.len() != index_strides.len()
158 || dest_dims.len() != dest_strides.len()
159 {
160 return Err(StridedError::StrideLengthMismatch);
161 }
162 checked_total_len(operand_dims)?;
163 checked_total_len(index_dims)?;
164 let total = checked_total_len(dest_dims)?;
165 if !crate::fused::is_injective_layout(dest_dims, dest_strides) {
166 return Err(StridedError::NonInjectiveOutputLayout);
167 }
168
169 let operand_rank = operand_dims.len();
170 if spec.slice_sizes.len() != operand_rank {
171 return Err(StridedError::RankMismatch(
172 spec.slice_sizes.len(),
173 operand_rank,
174 ));
175 }
176 validate_unique_axes(&spec.collapsed_slice_dims, operand_rank)?;
177 validate_unique_axes(&spec.start_index_map, operand_rank)?;
178 if spec.index_vector_dim > index_dims.len() {
179 return Err(StridedError::InvalidAxis {
180 axis: spec.index_vector_dim,
181 rank: index_dims.len() + 1,
182 });
183 }
184
185 for (axis, (&window, &dim)) in spec.slice_sizes.iter().zip(operand_dims.iter()).enumerate()
186 {
187 if window > dim {
188 return Err(StridedError::InvalidAxis {
189 axis,
190 rank: operand_rank,
191 });
192 }
193 }
194 for &axis in &spec.collapsed_slice_dims {
195 if spec.slice_sizes[axis] != 1 {
196 return Err(StridedError::InvalidAxis {
197 axis,
198 rank: operand_rank,
199 });
200 }
201 }
202
203 let index_vector_size = if spec.index_vector_dim == index_dims.len() {
204 1
205 } else {
206 index_dims[spec.index_vector_dim]
207 };
208 if index_vector_size != spec.start_index_map.len() {
209 return Err(StridedError::RankMismatch(
210 index_vector_size,
211 spec.start_index_map.len(),
212 ));
213 }
214
215 let window_dims = operand_window_dims(operand_rank, &spec.collapsed_slice_dims);
216 if spec.offset_dims.len() != window_dims.len() {
217 return Err(StridedError::RankMismatch(
218 spec.offset_dims.len(),
219 window_dims.len(),
220 ));
221 }
222
223 let batch_shape = index_batch_shape(index_dims, spec.index_vector_dim);
224 let out_rank = batch_shape.len() + spec.offset_dims.len();
225 validate_unique_axes(&spec.offset_dims, out_rank)?;
226
227 let mut out_axis_to_operand_dim: AxisVec<Option<usize>> =
228 (0..out_rank).map(|_| None).collect();
229 for (offset_axis, &out_axis) in spec.offset_dims.iter().enumerate() {
230 out_axis_to_operand_dim[out_axis] = Some(window_dims[offset_axis]);
231 }
232
233 let mut expected_dest_dims: AxisVec<usize> = AxisVec::with_capacity(out_rank);
234 let mut batch_axis = 0usize;
235 for &operand_dim in &out_axis_to_operand_dim {
236 match operand_dim {
237 Some(axis) => expected_dest_dims.push(spec.slice_sizes[axis]),
238 None => {
239 expected_dest_dims.push(batch_shape[batch_axis]);
240 batch_axis += 1;
241 }
242 }
243 }
244 if dest_dims != &expected_dest_dims[..] {
245 return Err(StridedError::ShapeMismatch(
246 dest_dims.to_vec(),
247 expected_dest_dims.to_vec(),
248 ));
249 }
250
251 Ok(Self {
252 operand_dims: operand_dims.into(),
253 operand_strides: operand_strides.into(),
254 index_dims: index_dims.into(),
255 index_strides: index_strides.into(),
256 dest_dims: dest_dims.into(),
257 dest_strides: dest_strides.into(),
258 spec,
259 batch_shape,
260 out_axis_to_operand_dim,
261 total,
262 })
263 }
264
265 #[inline]
266 pub fn spec(&self) -> &GatherSpec {
267 &self.spec
268 }
269
270 #[inline]
271 pub fn dest_dims(&self) -> &[usize] {
272 &self.dest_dims
273 }
274
275 pub fn execute<T, I>(
277 &self,
278 dest: &mut RawStridedMut<'_, T>,
279 operand: &RawStridedRef<'_, T>,
280 start_indices: &RawStridedRef<'_, I>,
281 ) -> Result<()>
282 where
283 T: Copy + MaybeSendSync,
284 I: GatherIndex,
285 {
286 self.execute_with_writer(dest, operand, start_indices)
287 }
288
289 pub(crate) fn execute_uninit<T, I>(
292 &self,
293 dest: &mut RawStridedMut<'_, MaybeUninit<T>>,
294 operand: &RawStridedRef<'_, T>,
295 start_indices: &RawStridedRef<'_, I>,
296 ) -> Result<()>
297 where
298 T: Copy + MaybeSendSync,
299 I: GatherIndex,
300 {
301 self.execute_with_writer(dest, operand, start_indices)
302 }
303
304 fn execute_with_writer<T, I, W>(
305 &self,
306 dest: &mut W,
307 operand: &RawStridedRef<'_, T>,
308 start_indices: &RawStridedRef<'_, I>,
309 ) -> Result<()>
310 where
311 T: Copy + MaybeSendSync,
312 I: GatherIndex,
313 W: OverwriteWriter<T>,
314 {
315 self.check_call(dest, operand, start_indices)?;
316 if self.total == 0 {
317 return Ok(());
318 }
319 #[cfg(feature = "parallel")]
320 {
321 let nthreads = crate::threading::parallel_threads_for_len(self.total);
322 if nthreads > 1 {
323 return self.execute_parallel(dest, operand, start_indices, nthreads);
324 }
325 }
326
327 let mut out_idx_storage = CoordScratch::new(self.dest_dims.len());
328 let mut batch_idx_storage = CoordScratch::new(self.batch_shape.len());
329 let mut operand_idx_storage = CoordScratch::new(self.operand_dims.len());
330 let mut window_offsets_storage = CoordScratch::new(self.operand_dims.len());
331 let out_idx = out_idx_storage.as_mut_slice();
332 let batch_idx = batch_idx_storage.as_mut_slice();
333 let operand_idx = operand_idx_storage.as_mut_slice();
334 let window_offsets = window_offsets_storage.as_mut_slice();
335
336 let dest_offset_base = dest.offset();
337 let operand_offset_base = operand.offset();
338 let operand_strides = operand.strides();
339 let index_offset_base = start_indices.offset();
340 let index_strides = start_indices.strides();
341 let operand_data = operand.data();
342 let index_data = start_indices.data();
343
344 for _ in 0..self.total {
345 window_offsets.fill(0);
346 let mut batch_axis = 0usize;
347 for (out_axis, &operand_dim) in self.out_axis_to_operand_dim.iter().enumerate() {
348 match operand_dim {
349 Some(axis) => window_offsets[axis] = out_idx[out_axis],
350 None => {
351 batch_idx[batch_axis] = out_idx[out_axis];
352 batch_axis += 1;
353 }
354 }
355 }
356
357 operand_idx.fill(0);
358 for (component, &operand_dim) in self.spec.start_index_map.iter().enumerate() {
359 let start = self.index_component(
360 start_indices.dims(),
361 index_strides,
362 index_offset_base,
363 index_data,
364 &batch_idx,
365 component,
366 )?;
367 operand_idx[operand_dim] = self.clamp_window_start(start, operand_dim);
368 }
369 for axis in 0..operand_idx.len() {
370 operand_idx[axis] += window_offsets[axis];
371 }
372
373 let dest_offset = checked_strided_offset(dest_offset_base, dest.strides(), &out_idx)?;
374 let operand_offset =
375 checked_strided_offset(operand_offset_base, operand_strides, &operand_idx)?;
376 let value = unsafe { *operand_data.as_ptr().offset(operand_offset) };
378 unsafe { dest.write_at(dest_offset, value) };
380 advance_col_major_index(out_idx, &self.dest_dims);
381 }
382 Ok(())
383 }
384
385 #[cfg(feature = "parallel")]
386 fn execute_parallel<T, I, W>(
387 &self,
388 dest: &mut W,
389 operand: &RawStridedRef<'_, T>,
390 start_indices: &RawStridedRef<'_, I>,
391 nthreads: usize,
392 ) -> Result<()>
393 where
394 T: Copy + MaybeSendSync,
395 I: GatherIndex,
396 W: OverwriteWriter<T>,
397 {
398 let dest_offset_base = dest.offset();
399 let operand_offset_base = operand.offset();
400 let index_offset_base = start_indices.offset();
401 let dest_ptr = crate::threading::SendPtr(unsafe { dest.data_ptr() });
403 let operand_ptr = crate::threading::SendPtr(operand.data().as_ptr() as *mut T);
404 let index_ptr = crate::threading::SendPtr(start_indices.data().as_ptr() as *mut I);
405
406 crate::threading::parallel_map_reduce(
407 0..self.total,
408 nthreads,
409 &|range| {
410 let mut out_idx_storage = CoordScratch::new(self.dest_dims.len());
411 let mut batch_idx_storage = CoordScratch::new(self.batch_shape.len());
412 let mut operand_idx_storage = CoordScratch::new(self.operand_dims.len());
413 let mut window_offsets_storage = CoordScratch::new(self.operand_dims.len());
414 let out_idx = out_idx_storage.as_mut_slice();
415 let batch_idx = batch_idx_storage.as_mut_slice();
416 let operand_idx = operand_idx_storage.as_mut_slice();
417 let window_offsets = window_offsets_storage.as_mut_slice();
418 fill_col_major_index(range.start, &self.dest_dims, out_idx);
419 let dest_ptr = dest_ptr.as_ptr();
420 let operand_ptr = operand_ptr.as_const();
421 let index_ptr = index_ptr.as_const();
422
423 for _ in range {
424 window_offsets.fill(0);
425 let mut batch_axis = 0usize;
426 for (out_axis, &operand_dim) in self.out_axis_to_operand_dim.iter().enumerate()
427 {
428 match operand_dim {
429 Some(axis) => window_offsets[axis] = out_idx[out_axis],
430 None => {
431 batch_idx[batch_axis] = out_idx[out_axis];
432 batch_axis += 1;
433 }
434 }
435 }
436
437 operand_idx.fill(0);
438 for (component, &operand_dim) in self.spec.start_index_map.iter().enumerate() {
439 let start = self.index_component_ptr(
440 start_indices.dims(),
441 index_offset_base,
442 index_ptr,
443 batch_idx,
444 component,
445 )?;
446 operand_idx[operand_dim] = self.clamp_window_start(start, operand_dim);
447 }
448 for axis in 0..operand_idx.len() {
449 operand_idx[axis] += window_offsets[axis];
450 }
451
452 let dest_offset =
453 checked_strided_offset(dest_offset_base, &self.dest_strides, out_idx)?;
454 let operand_offset = checked_strided_offset(
455 operand_offset_base,
456 &self.operand_strides,
457 operand_idx,
458 )?;
459 unsafe {
460 dest_ptr
464 .offset(dest_offset)
465 .write(operand_ptr.offset(operand_offset).read());
466 }
467 advance_col_major_index(out_idx, &self.dest_dims);
468 }
469 Ok(())
470 },
471 &|left, right| left.and(right),
472 )
473 }
474
475 fn check_call<T, I, W>(
476 &self,
477 dest: &W,
478 operand: &RawStridedRef<'_, T>,
479 start_indices: &RawStridedRef<'_, I>,
480 ) -> Result<()>
481 where
482 W: OverwriteWriter<T>,
483 {
484 if dest.dims() != &self.dest_dims[..]
485 || dest.strides() != &self.dest_strides[..]
486 || operand.dims() != &self.operand_dims[..]
487 || operand.strides() != &self.operand_strides[..]
488 || start_indices.dims() != &self.index_dims[..]
489 || start_indices.strides() != &self.index_strides[..]
490 {
491 return Err(StridedError::PlanLayoutMismatch);
492 }
493 Ok(())
494 }
495
496 fn index_component<I>(
497 &self,
498 index_dims: &[usize],
499 index_strides: &[isize],
500 index_offset_base: isize,
501 index_data: &[I],
502 batch_idx: &[usize],
503 component: usize,
504 ) -> Result<i64>
505 where
506 I: GatherIndex,
507 {
508 let mut offset = index_offset_base;
509 let mut batch_axis = 0usize;
510 for axis in 0..index_dims.len() {
511 let coord = if axis == self.spec.index_vector_dim {
512 component
513 } else {
514 let coord = batch_idx[batch_axis];
515 batch_axis += 1;
516 coord
517 };
518 offset = checked_offset_add(offset, index_strides[axis], coord)?;
519 }
520 Ok(unsafe { *index_data.as_ptr().offset(offset) }.to_i64())
521 }
522
523 #[cfg(feature = "parallel")]
524 fn index_component_ptr<I>(
525 &self,
526 index_dims: &[usize],
527 index_offset_base: isize,
528 index_ptr: *const I,
529 batch_idx: &[usize],
530 component: usize,
531 ) -> Result<i64>
532 where
533 I: GatherIndex,
534 {
535 let mut offset = index_offset_base;
536 let mut batch_axis = 0usize;
537 for axis in 0..index_dims.len() {
538 let coord = if axis == self.spec.index_vector_dim {
539 component
540 } else {
541 let coord = batch_idx[batch_axis];
542 batch_axis += 1;
543 coord
544 };
545 offset = checked_offset_add(offset, self.index_strides[axis], coord)?;
546 }
547 Ok(unsafe { *index_ptr.offset(offset) }.to_i64())
548 }
549
550 #[inline]
551 fn clamp_window_start(&self, start: i64, operand_dim: usize) -> usize {
552 let dim_size = self.operand_dims[operand_dim];
553 let window_size = self.spec.slice_sizes[operand_dim];
554 let max_start = dim_size.saturating_sub(window_size) as i64;
555 start.clamp(0, max_start) as usize
556 }
557}
558
559impl DynamicSlicePlan {
560 pub fn compile(
565 operand_dims: &[usize],
566 operand_strides: &[isize],
567 start_dims: &[usize],
568 start_strides: &[isize],
569 dest_dims: &[usize],
570 dest_strides: &[isize],
571 slice_sizes: &[usize],
572 ) -> Result<Self> {
573 if operand_dims.len() != operand_strides.len()
574 || start_dims.len() != start_strides.len()
575 || dest_dims.len() != dest_strides.len()
576 {
577 return Err(StridedError::StrideLengthMismatch);
578 }
579 if slice_sizes.len() != operand_dims.len() {
580 return Err(StridedError::RankMismatch(
581 slice_sizes.len(),
582 operand_dims.len(),
583 ));
584 }
585 validate_start_vector(start_dims, operand_dims.len())?;
586 checked_total_len(operand_dims)?;
587 checked_total_len(start_dims)?;
588 let total = checked_total_len(dest_dims)?;
589 if dest_dims != slice_sizes {
590 return Err(StridedError::ShapeMismatch(
591 dest_dims.to_vec(),
592 slice_sizes.to_vec(),
593 ));
594 }
595 if !crate::fused::is_injective_layout(dest_dims, dest_strides) {
596 return Err(StridedError::NonInjectiveOutputLayout);
597 }
598 validate_window_sizes(operand_dims, slice_sizes)?;
599
600 Ok(Self {
601 operand_dims: operand_dims.into(),
602 operand_strides: operand_strides.into(),
603 start_dims: start_dims.into(),
604 start_strides: start_strides.into(),
605 dest_dims: dest_dims.into(),
606 dest_strides: dest_strides.into(),
607 slice_sizes: slice_sizes.into(),
608 total,
609 })
610 }
611
612 pub fn execute<T, I>(
614 &self,
615 dest: &mut RawStridedMut<'_, T>,
616 operand: &RawStridedRef<'_, T>,
617 starts: &RawStridedRef<'_, I>,
618 ) -> Result<()>
619 where
620 T: Copy + MaybeSendSync,
621 I: GatherIndex,
622 {
623 self.execute_with_writer(dest, operand, starts)
624 }
625
626 pub(crate) fn execute_uninit<T, I>(
627 &self,
628 dest: &mut RawStridedMut<'_, MaybeUninit<T>>,
629 operand: &RawStridedRef<'_, T>,
630 starts: &RawStridedRef<'_, I>,
631 ) -> Result<()>
632 where
633 T: Copy + MaybeSendSync,
634 I: GatherIndex,
635 {
636 self.execute_with_writer(dest, operand, starts)
637 }
638
639 fn execute_with_writer<T, I, W>(
640 &self,
641 dest: &mut W,
642 operand: &RawStridedRef<'_, T>,
643 starts: &RawStridedRef<'_, I>,
644 ) -> Result<()>
645 where
646 T: Copy + MaybeSendSync,
647 I: GatherIndex,
648 W: OverwriteWriter<T>,
649 {
650 self.check_call(dest, operand, starts)?;
651 if self.total == 0 {
652 return Ok(());
653 }
654 if self.uses_rank_one_contiguous_path() {
655 return self.execute_rank_one_contiguous(dest, operand, starts);
656 }
657 #[cfg(feature = "parallel")]
658 {
659 let nthreads = crate::threading::parallel_threads_for_len(self.total);
660 if nthreads > 1 {
661 return self.execute_parallel(dest, operand, starts, nthreads);
662 }
663 }
664
665 let mut starts_storage = CoordScratch::new(self.operand_dims.len());
666 let mut dest_idx_storage = CoordScratch::new(self.dest_dims.len());
667 let mut operand_idx_storage = CoordScratch::new(self.operand_dims.len());
668 let clamped_starts = starts_storage.as_mut_slice();
669 let dest_idx = dest_idx_storage.as_mut_slice();
670 let operand_idx = operand_idx_storage.as_mut_slice();
671 read_clamped_starts(
672 starts,
673 &self.operand_dims,
674 &self.slice_sizes,
675 clamped_starts,
676 )?;
677
678 let operand_offset_base = operand.offset();
679 let operand_strides = operand.strides();
680 let dest_offset_base = dest.offset();
681 let operand_data = operand.data();
682
683 for _ in 0..self.total {
684 for axis in 0..operand_idx.len() {
685 operand_idx[axis] = clamped_starts[axis] + dest_idx[axis];
686 }
687 let operand_offset =
688 checked_strided_offset(operand_offset_base, operand_strides, operand_idx)?;
689 let dest_offset = checked_strided_offset(dest_offset_base, dest.strides(), dest_idx)?;
690 let value = unsafe { *operand_data.as_ptr().offset(operand_offset) };
692 unsafe { dest.write_at(dest_offset, value) };
694 advance_col_major_index(dest_idx, &self.dest_dims);
695 }
696 Ok(())
697 }
698
699 #[inline]
700 fn uses_rank_one_contiguous_path(&self) -> bool {
701 self.operand_dims.len() == 1 && self.operand_strides[0] == 1 && self.dest_strides[0] == 1
702 }
703
704 fn execute_rank_one_contiguous<T, I, W>(
705 &self,
706 dest: &mut W,
707 operand: &RawStridedRef<'_, T>,
708 starts: &RawStridedRef<'_, I>,
709 ) -> Result<()>
710 where
711 T: Copy,
712 I: GatherIndex,
713 W: OverwriteWriter<T>,
714 {
715 let mut clamped_starts = [0usize; 1];
716 read_clamped_starts(
717 starts,
718 &self.operand_dims,
719 &self.slice_sizes,
720 &mut clamped_starts,
721 )?;
722 let source_start = checked_offset_add(operand.offset(), 1, clamped_starts[0])?;
723 let source_start =
724 usize::try_from(source_start).map_err(|_| StridedError::OffsetOverflow)?;
725 let dest_start =
726 usize::try_from(dest.offset()).map_err(|_| StridedError::OffsetOverflow)?;
727 let source_end = source_start
728 .checked_add(self.total)
729 .ok_or(StridedError::OffsetOverflow)?;
730 let source = operand
731 .data()
732 .get(source_start..source_end)
733 .ok_or(StridedError::OffsetOverflow)?;
734 let dest_ptr = unsafe { dest.data_ptr() };
736 unsafe {
739 core::ptr::copy_nonoverlapping(source.as_ptr(), dest_ptr.add(dest_start), self.total);
740 }
741 Ok(())
742 }
743
744 #[cfg(feature = "parallel")]
745 fn execute_parallel<T, I, W>(
746 &self,
747 dest: &mut W,
748 operand: &RawStridedRef<'_, T>,
749 starts: &RawStridedRef<'_, I>,
750 nthreads: usize,
751 ) -> Result<()>
752 where
753 T: Copy + MaybeSendSync,
754 I: GatherIndex,
755 W: OverwriteWriter<T>,
756 {
757 let mut clamped_starts: AxisVec<usize> = (0..self.operand_dims.len()).map(|_| 0).collect();
758 read_clamped_starts(
759 starts,
760 &self.operand_dims,
761 &self.slice_sizes,
762 &mut clamped_starts,
763 )?;
764
765 let operand_offset_base = operand.offset();
766 let dest_offset_base = dest.offset();
767 let operand_ptr = crate::threading::SendPtr(operand.data().as_ptr() as *mut T);
768 let dest_ptr = crate::threading::SendPtr(unsafe { dest.data_ptr() });
770
771 crate::threading::parallel_map_reduce(
772 0..self.total,
773 nthreads,
774 &|range| {
775 let mut dest_idx_storage = CoordScratch::new(self.dest_dims.len());
776 let mut operand_idx_storage = CoordScratch::new(self.operand_dims.len());
777 let dest_idx = dest_idx_storage.as_mut_slice();
778 let operand_idx = operand_idx_storage.as_mut_slice();
779 fill_col_major_index(range.start, &self.dest_dims, dest_idx);
780 let operand_ptr = operand_ptr.as_const();
781 let dest_ptr = dest_ptr.as_ptr();
782
783 for _ in range {
784 for axis in 0..operand_idx.len() {
785 operand_idx[axis] = clamped_starts[axis] + dest_idx[axis];
786 }
787 let operand_offset = checked_strided_offset(
788 operand_offset_base,
789 &self.operand_strides,
790 operand_idx,
791 )?;
792 let dest_offset =
793 checked_strided_offset(dest_offset_base, &self.dest_strides, dest_idx)?;
794 unsafe {
795 dest_ptr
799 .offset(dest_offset)
800 .write(operand_ptr.offset(operand_offset).read());
801 }
802 advance_col_major_index(dest_idx, &self.dest_dims);
803 }
804 Ok(())
805 },
806 &|left, right| left.and(right),
807 )
808 }
809
810 fn check_call<T, I, W>(
811 &self,
812 dest: &W,
813 operand: &RawStridedRef<'_, T>,
814 starts: &RawStridedRef<'_, I>,
815 ) -> Result<()>
816 where
817 W: OverwriteWriter<T>,
818 {
819 if dest.dims() != &self.dest_dims[..]
820 || dest.strides() != &self.dest_strides[..]
821 || operand.dims() != &self.operand_dims[..]
822 || operand.strides() != &self.operand_strides[..]
823 || starts.dims() != &self.start_dims[..]
824 || starts.strides() != &self.start_strides[..]
825 {
826 return Err(StridedError::PlanLayoutMismatch);
827 }
828 Ok(())
829 }
830}
831
832impl DynamicUpdateSlicePlan {
833 #[allow(clippy::too_many_arguments)]
838 pub fn compile(
839 operand_dims: &[usize],
840 operand_strides: &[isize],
841 start_dims: &[usize],
842 start_strides: &[isize],
843 update_dims: &[usize],
844 update_strides: &[isize],
845 dest_dims: &[usize],
846 dest_strides: &[isize],
847 ) -> Result<Self> {
848 if operand_dims.len() != operand_strides.len()
849 || start_dims.len() != start_strides.len()
850 || update_dims.len() != update_strides.len()
851 || dest_dims.len() != dest_strides.len()
852 {
853 return Err(StridedError::StrideLengthMismatch);
854 }
855 if update_dims.len() != operand_dims.len() {
856 return Err(StridedError::RankMismatch(
857 update_dims.len(),
858 operand_dims.len(),
859 ));
860 }
861 validate_start_vector(start_dims, operand_dims.len())?;
862 checked_total_len(operand_dims)?;
863 checked_total_len(start_dims)?;
864 let total = checked_total_len(update_dims)?;
865 if dest_dims != operand_dims {
866 return Err(StridedError::ShapeMismatch(
867 dest_dims.to_vec(),
868 operand_dims.to_vec(),
869 ));
870 }
871 if !crate::fused::is_injective_layout(dest_dims, dest_strides) {
872 return Err(StridedError::NonInjectiveOutputLayout);
873 }
874 validate_window_sizes(operand_dims, update_dims)?;
875 let copy_plan = CopyPlan::compile(operand_dims, dest_strides, operand_strides)?;
876
877 Ok(Self {
878 operand_dims: operand_dims.into(),
879 operand_strides: operand_strides.into(),
880 start_dims: start_dims.into(),
881 start_strides: start_strides.into(),
882 update_dims: update_dims.into(),
883 update_strides: update_strides.into(),
884 dest_dims: dest_dims.into(),
885 dest_strides: dest_strides.into(),
886 total,
887 copy_plan,
888 })
889 }
890
891 pub fn execute<T, I>(
893 &self,
894 dest: &mut RawStridedMut<'_, T>,
895 operand: &RawStridedRef<'_, T>,
896 update: &RawStridedRef<'_, T>,
897 starts: &RawStridedRef<'_, I>,
898 ) -> Result<()>
899 where
900 T: Copy + MaybeSendSync,
901 I: GatherIndex,
902 {
903 self.check_call(dest, operand, update, starts)?;
904 self.copy_plan.execute(dest, operand)?;
905 self.execute_update_with_writer(dest, update, starts)
906 }
907
908 pub(crate) fn execute_uninit<'a, T, I>(
911 &self,
912 dest: &'a mut RawStridedMut<'a, MaybeUninit<T>>,
913 operand: &RawStridedRef<'_, T>,
914 update: &RawStridedRef<'_, T>,
915 starts: &RawStridedRef<'_, I>,
916 ) -> Result<()>
917 where
918 T: Copy + MaybeSendSync,
919 I: GatherIndex,
920 {
921 self.check_call(dest, operand, update, starts)?;
922 self.copy_plan
923 .execute_uninit_then(dest, operand, |mut receipt| {
924 self.execute_update_with_writer(&mut receipt, update, starts)
925 })?
926 }
927
928 fn execute_update_with_writer<T, I, W>(
929 &self,
930 dest: &mut W,
931 update: &RawStridedRef<'_, T>,
932 starts: &RawStridedRef<'_, I>,
933 ) -> Result<()>
934 where
935 T: Copy + MaybeSendSync,
936 I: GatherIndex,
937 W: OverwriteWriter<T>,
938 {
939 if self.total == 0 {
940 return Ok(());
941 }
942 if self.uses_rank_one_contiguous_path() {
943 return self.execute_rank_one_contiguous(dest, update, starts);
944 }
945 #[cfg(feature = "parallel")]
946 {
947 let nthreads = crate::threading::parallel_threads_for_len(self.total);
948 if nthreads > 1 {
949 return self.execute_update_parallel(dest, update, starts, nthreads);
950 }
951 }
952
953 let mut starts_storage = CoordScratch::new(self.operand_dims.len());
954 let mut update_idx_storage = CoordScratch::new(self.update_dims.len());
955 let mut dest_idx_storage = CoordScratch::new(self.operand_dims.len());
956 let clamped_starts = starts_storage.as_mut_slice();
957 let update_idx = update_idx_storage.as_mut_slice();
958 let dest_idx = dest_idx_storage.as_mut_slice();
959 read_clamped_starts(
960 starts,
961 &self.operand_dims,
962 &self.update_dims,
963 clamped_starts,
964 )?;
965
966 let update_offset_base = update.offset();
967 let update_strides = update.strides();
968 let dest_offset_base = dest.offset();
969 let update_data = update.data();
970
971 for _ in 0..self.total {
972 for axis in 0..dest_idx.len() {
973 dest_idx[axis] = clamped_starts[axis] + update_idx[axis];
974 }
975 let update_offset =
976 checked_strided_offset(update_offset_base, update_strides, update_idx)?;
977 let dest_offset = checked_strided_offset(dest_offset_base, dest.strides(), dest_idx)?;
978 let value = unsafe { *update_data.as_ptr().offset(update_offset) };
979 unsafe { dest.write_at(dest_offset, value) };
981 advance_col_major_index(update_idx, &self.update_dims);
982 }
983 Ok(())
984 }
985
986 #[inline]
987 fn uses_rank_one_contiguous_path(&self) -> bool {
988 self.operand_dims.len() == 1
989 && self.operand_strides[0] == 1
990 && self.update_strides[0] == 1
991 && self.dest_strides[0] == 1
992 }
993
994 fn execute_rank_one_contiguous<T, I, W>(
995 &self,
996 dest: &mut W,
997 update: &RawStridedRef<'_, T>,
998 starts: &RawStridedRef<'_, I>,
999 ) -> Result<()>
1000 where
1001 T: Copy,
1002 I: GatherIndex,
1003 W: OverwriteWriter<T>,
1004 {
1005 let mut clamped_starts = [0usize; 1];
1006 read_clamped_starts(
1007 starts,
1008 &self.operand_dims,
1009 &self.update_dims,
1010 &mut clamped_starts,
1011 )?;
1012 let update_start =
1013 usize::try_from(update.offset()).map_err(|_| StridedError::OffsetOverflow)?;
1014 let dest_start = checked_offset_add(dest.offset(), 1, clamped_starts[0])?;
1015 let dest_start = usize::try_from(dest_start).map_err(|_| StridedError::OffsetOverflow)?;
1016 let update_end = update_start
1017 .checked_add(self.total)
1018 .ok_or(StridedError::OffsetOverflow)?;
1019 let update = update
1020 .data()
1021 .get(update_start..update_end)
1022 .ok_or(StridedError::OffsetOverflow)?;
1023 let dest_ptr = unsafe { dest.data_ptr() };
1025 unsafe {
1027 core::ptr::copy_nonoverlapping(update.as_ptr(), dest_ptr.add(dest_start), self.total);
1028 }
1029 Ok(())
1030 }
1031
1032 #[cfg(feature = "parallel")]
1033 fn execute_update_parallel<T, I, W>(
1034 &self,
1035 dest: &mut W,
1036 update: &RawStridedRef<'_, T>,
1037 starts: &RawStridedRef<'_, I>,
1038 nthreads: usize,
1039 ) -> Result<()>
1040 where
1041 T: Copy + MaybeSendSync,
1042 I: GatherIndex,
1043 W: OverwriteWriter<T>,
1044 {
1045 let mut clamped_starts: AxisVec<usize> = (0..self.operand_dims.len()).map(|_| 0).collect();
1046 read_clamped_starts(
1047 starts,
1048 &self.operand_dims,
1049 &self.update_dims,
1050 &mut clamped_starts,
1051 )?;
1052
1053 let update_offset_base = update.offset();
1054 let dest_offset_base = dest.offset();
1055 let update_ptr = crate::threading::SendPtr(update.data().as_ptr() as *mut T);
1056 let dest_ptr = crate::threading::SendPtr(unsafe { dest.data_ptr() });
1058
1059 crate::threading::parallel_map_reduce(
1060 0..self.total,
1061 nthreads,
1062 &|range| {
1063 let mut update_idx_storage = CoordScratch::new(self.update_dims.len());
1064 let mut dest_idx_storage = CoordScratch::new(self.operand_dims.len());
1065 let update_idx = update_idx_storage.as_mut_slice();
1066 let dest_idx = dest_idx_storage.as_mut_slice();
1067 fill_col_major_index(range.start, &self.update_dims, update_idx);
1068 let update_ptr = update_ptr.as_const();
1069 let dest_ptr = dest_ptr.as_ptr();
1070
1071 for _ in range {
1072 for axis in 0..dest_idx.len() {
1073 dest_idx[axis] = clamped_starts[axis] + update_idx[axis];
1074 }
1075 let update_offset = checked_strided_offset(
1076 update_offset_base,
1077 &self.update_strides,
1078 update_idx,
1079 )?;
1080 let dest_offset =
1081 checked_strided_offset(dest_offset_base, &self.dest_strides, dest_idx)?;
1082 unsafe {
1083 dest_ptr
1087 .offset(dest_offset)
1088 .write(update_ptr.offset(update_offset).read());
1089 }
1090 advance_col_major_index(update_idx, &self.update_dims);
1091 }
1092 Ok(())
1093 },
1094 &|left, right| left.and(right),
1095 )
1096 }
1097
1098 fn check_call<T, I, W>(
1099 &self,
1100 dest: &W,
1101 operand: &RawStridedRef<'_, T>,
1102 update: &RawStridedRef<'_, T>,
1103 starts: &RawStridedRef<'_, I>,
1104 ) -> Result<()>
1105 where
1106 W: OverwriteWriter<T>,
1107 {
1108 if dest.dims() != &self.dest_dims[..]
1109 || dest.strides() != &self.dest_strides[..]
1110 || operand.dims() != &self.operand_dims[..]
1111 || operand.strides() != &self.operand_strides[..]
1112 || update.dims() != &self.update_dims[..]
1113 || update.strides() != &self.update_strides[..]
1114 || starts.dims() != &self.start_dims[..]
1115 || starts.strides() != &self.start_strides[..]
1116 {
1117 return Err(StridedError::PlanLayoutMismatch);
1118 }
1119 Ok(())
1120 }
1121}
1122
1123impl ScatterPlan {
1124 #[allow(clippy::too_many_arguments)]
1126 pub fn compile(
1127 operand_dims: &[usize],
1128 operand_strides: &[isize],
1129 index_dims: &[usize],
1130 index_strides: &[isize],
1131 update_dims: &[usize],
1132 update_strides: &[isize],
1133 dest_dims: &[usize],
1134 dest_strides: &[isize],
1135 spec: ScatterSpec,
1136 ) -> Result<Self> {
1137 if operand_dims.len() != operand_strides.len()
1138 || index_dims.len() != index_strides.len()
1139 || update_dims.len() != update_strides.len()
1140 || dest_dims.len() != dest_strides.len()
1141 {
1142 return Err(StridedError::StrideLengthMismatch);
1143 }
1144 checked_total_len(operand_dims)?;
1145 checked_total_len(index_dims)?;
1146 checked_total_len(update_dims)?;
1147 if dest_dims != operand_dims {
1148 return Err(StridedError::ShapeMismatch(
1149 dest_dims.to_vec(),
1150 operand_dims.to_vec(),
1151 ));
1152 }
1153 if !crate::fused::is_injective_layout(dest_dims, dest_strides) {
1154 return Err(StridedError::NonInjectiveOutputLayout);
1155 }
1156
1157 let operand_rank = operand_dims.len();
1158 validate_unique_axes(&spec.inserted_window_dims, operand_rank)?;
1159 validate_unique_axes(&spec.scatter_dims_to_operand_dims, operand_rank)?;
1160 if spec.index_vector_dim > index_dims.len() {
1161 return Err(StridedError::InvalidAxis {
1162 axis: spec.index_vector_dim,
1163 rank: index_dims.len() + 1,
1164 });
1165 }
1166 let index_vector_size = if spec.index_vector_dim == index_dims.len() {
1167 1
1168 } else {
1169 index_dims[spec.index_vector_dim]
1170 };
1171 if index_vector_size != spec.scatter_dims_to_operand_dims.len() {
1172 return Err(StridedError::RankMismatch(
1173 index_vector_size,
1174 spec.scatter_dims_to_operand_dims.len(),
1175 ));
1176 }
1177
1178 let batch_shape = index_batch_shape(index_dims, spec.index_vector_dim);
1179 let window_dims = operand_window_dims(operand_rank, &spec.inserted_window_dims);
1180 if spec.update_window_dims.len() != window_dims.len() {
1181 return Err(StridedError::RankMismatch(
1182 spec.update_window_dims.len(),
1183 window_dims.len(),
1184 ));
1185 }
1186
1187 let update_rank = update_dims.len();
1188 let expected_batch_rank = update_rank
1189 .checked_sub(spec.update_window_dims.len())
1190 .ok_or(StridedError::RankMismatch(
1191 spec.update_window_dims.len(),
1192 update_rank,
1193 ))?;
1194 if expected_batch_rank != batch_shape.len() {
1195 return Err(StridedError::RankMismatch(
1196 expected_batch_rank,
1197 batch_shape.len(),
1198 ));
1199 }
1200 validate_unique_axes(&spec.update_window_dims, update_rank)?;
1201
1202 let mut is_update_window_dim: AxisVec<bool> = (0..update_rank).map(|_| false).collect();
1203 for &axis in &spec.update_window_dims {
1204 is_update_window_dim[axis] = true;
1205 }
1206
1207 let mut batch_axis = 0usize;
1208 for axis in 0..update_rank {
1209 if !is_update_window_dim[axis] {
1210 if update_dims[axis] != batch_shape[batch_axis] {
1211 return Err(StridedError::ShapeMismatch(
1212 update_dims.to_vec(),
1213 expected_scatter_update_shape(&batch_shape, &spec, update_dims).to_vec(),
1214 ));
1215 }
1216 batch_axis += 1;
1217 }
1218 }
1219
1220 let mut window_shape: AxisVec<usize> = (0..operand_rank).map(|_| 1).collect();
1221 let mut window_shape_updates: AxisVec<usize> =
1222 AxisVec::with_capacity(spec.update_window_dims.len());
1223 for (pos, &update_axis) in spec.update_window_dims.iter().enumerate() {
1224 let dim = update_dims[update_axis];
1225 window_shape_updates.push(dim);
1226 window_shape[window_dims[pos]] = dim;
1227 }
1228 validate_window_sizes(operand_dims, &window_shape)?;
1229
1230 let batch_elems = checked_total_len(&batch_shape)?;
1231 let window_elems = checked_total_len(&window_shape_updates)?;
1232 let copy_plan = CopyPlan::compile(operand_dims, dest_strides, operand_strides)?;
1233
1234 Ok(Self {
1235 operand_dims: operand_dims.into(),
1236 operand_strides: operand_strides.into(),
1237 index_dims: index_dims.into(),
1238 index_strides: index_strides.into(),
1239 update_dims: update_dims.into(),
1240 update_strides: update_strides.into(),
1241 dest_dims: dest_dims.into(),
1242 dest_strides: dest_strides.into(),
1243 spec,
1244 batch_shape,
1245 window_dims,
1246 window_shape,
1247 window_shape_updates,
1248 is_update_window_dim,
1249 batch_elems,
1250 window_elems,
1251 copy_plan,
1252 })
1253 }
1254
1255 pub fn execute<T, I>(
1257 &self,
1258 dest: &mut RawStridedMut<'_, T>,
1259 operand: &RawStridedRef<'_, T>,
1260 scatter_indices: &RawStridedRef<'_, I>,
1261 updates: &RawStridedRef<'_, T>,
1262 ) -> Result<()>
1263 where
1264 T: Copy + Add<Output = T> + MaybeSendSync,
1265 I: GatherIndex,
1266 {
1267 self.check_call(dest, operand, scatter_indices, updates)?;
1268 self.copy_plan.execute(dest, operand)?;
1269 self.execute_updates(dest, scatter_indices, updates, |a, b| a + b)
1270 }
1271
1272 pub(crate) fn execute_uninit<'a, T, I>(
1275 &self,
1276 dest: &'a mut RawStridedMut<'a, MaybeUninit<T>>,
1277 operand: &RawStridedRef<'_, T>,
1278 scatter_indices: &RawStridedRef<'_, I>,
1279 updates: &RawStridedRef<'_, T>,
1280 combine: fn(T, T) -> T,
1281 ) -> Result<()>
1282 where
1283 T: Copy + Add<Output = T> + MaybeSendSync,
1284 I: GatherIndex,
1285 {
1286 self.check_call(dest, operand, scatter_indices, updates)?;
1287 self.copy_plan
1288 .execute_uninit_then(dest, operand, |mut receipt| {
1289 self.execute_updates(&mut receipt, scatter_indices, updates, combine)
1290 })?
1291 }
1292
1293 fn execute_updates<T, I, W>(
1294 &self,
1295 dest: &mut W,
1296 scatter_indices: &RawStridedRef<'_, I>,
1297 updates: &RawStridedRef<'_, T>,
1298 combine: fn(T, T) -> T,
1299 ) -> Result<()>
1300 where
1301 T: Copy + MaybeSendSync,
1302 I: GatherIndex,
1303 W: ReadModifyWrite<T>,
1304 {
1305 if self.batch_elems == 0 || self.window_elems == 0 {
1306 return Ok(());
1307 }
1308
1309 let mut batch_idx_storage = CoordScratch::new(self.batch_shape.len());
1312 let mut window_idx_storage = CoordScratch::new(self.window_shape_updates.len());
1313 let mut update_idx_storage = CoordScratch::new(self.update_dims.len());
1314 let mut operand_base_storage = CoordScratch::new(self.operand_dims.len());
1315 let mut operand_idx_storage = CoordScratch::new(self.operand_dims.len());
1316 let batch_idx = batch_idx_storage.as_mut_slice();
1317 let window_idx = window_idx_storage.as_mut_slice();
1318 let update_idx = update_idx_storage.as_mut_slice();
1319 let operand_base = operand_base_storage.as_mut_slice();
1320 let operand_idx = operand_idx_storage.as_mut_slice();
1321
1322 let index_offset_base = scatter_indices.offset();
1323 let index_strides = scatter_indices.strides();
1324 let index_data = scatter_indices.data();
1325 let update_offset_base = updates.offset();
1326 let update_strides = updates.strides();
1327 let update_data = updates.data();
1328 let dest_offset_base = dest.offset();
1329
1330 for _ in 0..self.batch_elems {
1331 operand_base.fill(0);
1332 for (component, &operand_dim) in
1333 self.spec.scatter_dims_to_operand_dims.iter().enumerate()
1334 {
1335 let start = index_component(
1336 scatter_indices.dims(),
1337 index_strides,
1338 index_offset_base,
1339 index_data,
1340 self.spec.index_vector_dim,
1341 batch_idx,
1342 component,
1343 )?;
1344 operand_base[operand_dim] = clamp_window_start(
1345 start,
1346 self.operand_dims[operand_dim],
1347 self.window_shape[operand_dim],
1348 );
1349 }
1350
1351 window_idx.fill(0);
1352 for _ in 0..self.window_elems {
1353 let mut batch_axis = 0usize;
1354 let mut window_axis = 0usize;
1355 for axis in 0..self.update_dims.len() {
1356 if self.is_update_window_dim[axis] {
1357 update_idx[axis] = window_idx[window_axis];
1358 window_axis += 1;
1359 } else {
1360 update_idx[axis] = batch_idx[batch_axis];
1361 batch_axis += 1;
1362 }
1363 }
1364
1365 operand_idx.copy_from_slice(operand_base);
1366 for (window_axis, &operand_axis) in self.window_dims.iter().enumerate() {
1367 operand_idx[operand_axis] += window_idx[window_axis];
1368 }
1369
1370 let update_offset =
1371 checked_strided_offset(update_offset_base, update_strides, update_idx)?;
1372 let dest_offset =
1373 checked_strided_offset(dest_offset_base, dest.strides(), operand_idx)?;
1374 let value = unsafe { *update_data.as_ptr().offset(update_offset) };
1375 unsafe { dest.add_at(dest_offset, value, combine) };
1378 advance_col_major_index(window_idx, &self.window_shape_updates);
1379 }
1380 advance_col_major_index(batch_idx, &self.batch_shape);
1381 }
1382 Ok(())
1383 }
1384
1385 fn check_call<T, I, W>(
1386 &self,
1387 dest: &W,
1388 operand: &RawStridedRef<'_, T>,
1389 scatter_indices: &RawStridedRef<'_, I>,
1390 updates: &RawStridedRef<'_, T>,
1391 ) -> Result<()>
1392 where
1393 W: OverwriteWriter<T>,
1394 {
1395 if dest.dims() != &self.dest_dims[..]
1396 || dest.strides() != &self.dest_strides[..]
1397 || operand.dims() != &self.operand_dims[..]
1398 || operand.strides() != &self.operand_strides[..]
1399 || scatter_indices.dims() != &self.index_dims[..]
1400 || scatter_indices.strides() != &self.index_strides[..]
1401 || updates.dims() != &self.update_dims[..]
1402 || updates.strides() != &self.update_strides[..]
1403 {
1404 return Err(StridedError::PlanLayoutMismatch);
1405 }
1406 Ok(())
1407 }
1408}
1409
1410fn validate_unique_axes(axes: &[usize], rank: usize) -> Result<()> {
1411 let mut seen = vec![false; rank];
1412 for &axis in axes {
1413 if axis >= rank {
1414 return Err(StridedError::InvalidAxis { axis, rank });
1415 }
1416 if seen[axis] {
1417 return Err(StridedError::InvalidAxis { axis, rank });
1418 }
1419 seen[axis] = true;
1420 }
1421 Ok(())
1422}
1423
1424fn validate_start_vector(start_dims: &[usize], operand_rank: usize) -> Result<()> {
1425 if start_dims.len() != 1 {
1426 return Err(StridedError::RankMismatch(start_dims.len(), 1));
1427 }
1428 if start_dims[0] != operand_rank {
1429 return Err(StridedError::RankMismatch(start_dims[0], operand_rank));
1430 }
1431 Ok(())
1432}
1433
1434fn validate_window_sizes(operand_dims: &[usize], window_sizes: &[usize]) -> Result<()> {
1435 if operand_dims.len() != window_sizes.len() {
1436 return Err(StridedError::RankMismatch(
1437 window_sizes.len(),
1438 operand_dims.len(),
1439 ));
1440 }
1441 for (axis, (&window, &dim)) in window_sizes.iter().zip(operand_dims.iter()).enumerate() {
1442 if window > dim {
1443 return Err(StridedError::InvalidAxis {
1444 axis,
1445 rank: operand_dims.len(),
1446 });
1447 }
1448 }
1449 Ok(())
1450}
1451
1452fn read_clamped_starts<I>(
1453 starts: &RawStridedRef<'_, I>,
1454 operand_dims: &[usize],
1455 window_sizes: &[usize],
1456 out: &mut [usize],
1457) -> Result<()>
1458where
1459 I: GatherIndex,
1460{
1461 debug_assert_eq!(operand_dims.len(), window_sizes.len());
1462 debug_assert_eq!(operand_dims.len(), out.len());
1463 for axis in 0..operand_dims.len() {
1464 let offset = checked_offset_add(starts.offset(), starts.strides()[0], axis)?;
1465 let start = unsafe { *starts.data().as_ptr().offset(offset) }.to_i64();
1466 out[axis] = clamp_window_start(start, operand_dims[axis], window_sizes[axis]);
1467 }
1468 Ok(())
1469}
1470
1471fn index_component<I>(
1472 index_dims: &[usize],
1473 index_strides: &[isize],
1474 index_offset_base: isize,
1475 index_data: &[I],
1476 index_vector_dim: usize,
1477 batch_idx: &[usize],
1478 component: usize,
1479) -> Result<i64>
1480where
1481 I: GatherIndex,
1482{
1483 let mut offset = index_offset_base;
1484 let mut batch_axis = 0usize;
1485 for axis in 0..index_dims.len() {
1486 let coord = if axis == index_vector_dim {
1487 component
1488 } else {
1489 let coord = batch_idx[batch_axis];
1490 batch_axis += 1;
1491 coord
1492 };
1493 offset = checked_offset_add(offset, index_strides[axis], coord)?;
1494 }
1495 Ok(unsafe { *index_data.as_ptr().offset(offset) }.to_i64())
1496}
1497
1498#[inline]
1499fn clamp_window_start(start: i64, dim_size: usize, window_size: usize) -> usize {
1500 let max_start = dim_size.saturating_sub(window_size) as i64;
1501 start.clamp(0, max_start) as usize
1502}
1503
1504fn expected_scatter_update_shape(
1505 batch_shape: &[usize],
1506 spec: &ScatterSpec,
1507 update_dims: &[usize],
1508) -> AxisVec<usize> {
1509 let mut expected: AxisVec<usize> = AxisVec::with_capacity(update_dims.len());
1510 let mut batch_axis = 0usize;
1511 for axis in 0..update_dims.len() {
1512 if spec.update_window_dims.contains(&axis) {
1513 expected.push(update_dims[axis]);
1514 } else {
1515 expected.push(batch_shape[batch_axis]);
1516 batch_axis += 1;
1517 }
1518 }
1519 expected
1520}
1521
1522fn operand_window_dims(rank: usize, collapsed_slice_dims: &[usize]) -> AxisVec<usize> {
1523 (0..rank)
1524 .filter(|axis| !collapsed_slice_dims.contains(axis))
1525 .collect()
1526}
1527
1528fn index_batch_shape(index_dims: &[usize], index_vector_dim: usize) -> AxisVec<usize> {
1529 if index_vector_dim == index_dims.len() {
1530 return index_dims.into();
1531 }
1532 index_dims
1533 .iter()
1534 .enumerate()
1535 .filter_map(|(axis, &dim)| (axis != index_vector_dim).then_some(dim))
1536 .collect()
1537}
1538
1539fn checked_total_len(dims: &[usize]) -> Result<usize> {
1540 if dims.is_empty() {
1541 return Ok(1);
1542 }
1543 dims.iter()
1544 .try_fold(1usize, |acc, &dim| acc.checked_mul(dim))
1545 .ok_or(StridedError::OffsetOverflow)
1546}
1547
1548fn checked_strided_offset(base: isize, strides: &[isize], index: &[usize]) -> Result<isize> {
1549 let mut offset = base;
1550 for (&stride, &coord) in strides.iter().zip(index.iter()) {
1551 offset = checked_offset_add(offset, stride, coord)?;
1552 }
1553 Ok(offset)
1554}
1555
1556fn checked_offset_add(base: isize, stride: isize, coord: usize) -> Result<isize> {
1557 let coord = isize::try_from(coord).map_err(|_| StridedError::OffsetOverflow)?;
1558 let scaled = stride
1559 .checked_mul(coord)
1560 .ok_or(StridedError::OffsetOverflow)?;
1561 base.checked_add(scaled).ok_or(StridedError::OffsetOverflow)
1562}
1563
1564fn advance_col_major_index(index: &mut [usize], shape: &[usize]) {
1565 for axis in 0..index.len() {
1566 index[axis] += 1;
1567 if index[axis] < shape[axis] {
1568 return;
1569 }
1570 index[axis] = 0;
1571 }
1572}
1573
1574#[cfg(feature = "parallel")]
1575fn fill_col_major_index(mut linear: usize, shape: &[usize], out: &mut [usize]) {
1576 for (axis, coord) in out.iter_mut().enumerate() {
1577 let dim = shape[axis];
1578 *coord = linear % dim;
1579 linear /= dim;
1580 }
1581}
1582
1583struct CoordScratch {
1584 inline: [usize; RAW_FUSED_RANK_LIMIT],
1585 heap: Option<Vec<usize>>,
1586 len: usize,
1587}
1588
1589impl CoordScratch {
1590 fn new(len: usize) -> Self {
1591 if len <= RAW_FUSED_RANK_LIMIT {
1592 Self {
1593 inline: [0; RAW_FUSED_RANK_LIMIT],
1594 heap: None,
1595 len,
1596 }
1597 } else {
1598 Self {
1599 inline: [0; RAW_FUSED_RANK_LIMIT],
1600 heap: Some(vec![0; len]),
1601 len,
1602 }
1603 }
1604 }
1605
1606 fn as_mut_slice(&mut self) -> &mut [usize] {
1607 match &mut self.heap {
1608 Some(heap) => heap,
1609 None => &mut self.inline[..self.len],
1610 }
1611 }
1612}
1613
1614#[cfg(test)]
1615mod tests {
1616 use super::{DynamicSlicePlan, DynamicUpdateSlicePlan};
1617
1618 #[test]
1619 fn dynamic_slice_fast_path_is_limited_to_rank_one_contiguous_layouts() {
1620 let contiguous =
1621 DynamicSlicePlan::compile(&[16], &[1], &[1], &[1], &[8], &[1], &[8]).unwrap();
1622 assert!(contiguous.uses_rank_one_contiguous_path());
1623
1624 let higher_rank =
1625 DynamicSlicePlan::compile(&[4, 4], &[1, 4], &[2], &[1], &[2, 2], &[1, 2], &[2, 2])
1626 .unwrap();
1627 assert!(!higher_rank.uses_rank_one_contiguous_path());
1628
1629 let strided = DynamicSlicePlan::compile(&[16], &[2], &[1], &[1], &[8], &[2], &[8]).unwrap();
1630 assert!(!strided.uses_rank_one_contiguous_path());
1631 }
1632
1633 #[test]
1634 fn dynamic_update_fast_path_is_limited_to_rank_one_contiguous_layouts() {
1635 let contiguous =
1636 DynamicUpdateSlicePlan::compile(&[16], &[1], &[1], &[1], &[8], &[1], &[16], &[1])
1637 .unwrap();
1638 assert!(contiguous.uses_rank_one_contiguous_path());
1639
1640 let higher_rank = DynamicUpdateSlicePlan::compile(
1641 &[4, 4],
1642 &[1, 4],
1643 &[2],
1644 &[1],
1645 &[2, 2],
1646 &[1, 2],
1647 &[4, 4],
1648 &[1, 4],
1649 )
1650 .unwrap();
1651 assert!(!higher_rank.uses_rank_one_contiguous_path());
1652
1653 let strided =
1654 DynamicUpdateSlicePlan::compile(&[16], &[2], &[1], &[1], &[8], &[2], &[16], &[2])
1655 .unwrap();
1656 assert!(!strided.uses_rank_one_contiguous_path());
1657 }
1658}