strided_basic/copy_plan.rs
1//! Prepared (compile-once, execute-many) copy plans over raw strided layouts.
2//!
3//! [`copy_scale_raw`](crate::copy_scale_raw) and friends rebuild the fused
4//! loop nest on every call; for prepared-replay consumers that issue many
5//! small copies with a fixed layout, that per-call planning dominates
6//! (see issue #139). [`CopyPlan`] splits the work: [`CopyPlan::compile`]
7//! validates the layout pair and builds the fused traversal once,
8//! [`CopyPlan::execute`]/[`CopyPlan::execute_scale`]/[`CopyPlan::execute_conj`]
9//! replay it with no planning and no heap allocation for fused ranks at most
10//! [`RAW_FUSED_RANK_LIMIT`](crate::RAW_FUSED_RANK_LIMIT).
11
12use core::{
13 marker::PhantomData,
14 mem::MaybeUninit,
15 ops::{Add, Mul},
16};
17
18use num_traits::{One, Zero};
19
20use crate::raw_ops::{
21 apply_fused_range, apply_spilled_range, fuse_pair_traversal, fused_total, FusedAxis,
22 FusedPairLayout, PairTraversal,
23};
24use crate::{ElementOpApply, MaybeSendSync, RawStridedMut, RawStridedRef, Result, StridedError};
25
26// Same pattern as map_view.rs / outer_product.rs: inline storage in both
27// feature configurations. Only `compile` touches these; `execute*` never
28// allocates for them. (A spilled fused layout needs one cursor allocation per
29// replayed range, so an `execute*` can allocate once per worker range above the
30// inline rank bound.)
31type AxisVec<T> = crate::inline::InlineVec<T>;
32
33pub(crate) trait OverwriteWriter<T> {
34 fn dims(&self) -> &[usize];
35 fn strides(&self) -> &[isize];
36 fn offset(&self) -> isize;
37 /// # Safety
38 /// The caller must use the pointer only for the validated allocation and
39 /// logical layout represented by this writer.
40 unsafe fn data_ptr(&mut self) -> *mut T;
41 /// # Safety
42 /// The offset must be an in-bounds logical destination proven by layout.
43 unsafe fn write_at(&mut self, offset: isize, value: T);
44}
45
46pub(crate) trait ReadModifyWrite<T>: OverwriteWriter<T> {
47 /// # Safety
48 /// The offset must be an in-bounds initialized slot covered by the
49 /// traversal's copy and disjointness proof.
50 unsafe fn add_at<F>(&mut self, offset: isize, value: T, combine: F)
51 where
52 F: FnOnce(T, T) -> T;
53}
54
55impl<'a, T> OverwriteWriter<T> for RawStridedMut<'a, T> {
56 fn dims(&self) -> &[usize] {
57 self.dims()
58 }
59 fn strides(&self) -> &[isize] {
60 self.strides()
61 }
62 fn offset(&self) -> isize {
63 self.offset()
64 }
65 unsafe fn data_ptr(&mut self) -> *mut T {
66 self.data_mut().as_mut_ptr()
67 }
68 unsafe fn write_at(&mut self, offset: isize, value: T) {
69 // SAFETY: the prepared layout validates every logical destination.
70 unsafe { self.data_mut().as_mut_ptr().offset(offset).write(value) }
71 }
72}
73
74impl<'a, T> ReadModifyWrite<T> for RawStridedMut<'a, T>
75where
76 T: Add<Output = T>,
77{
78 #[inline(always)]
79 unsafe fn add_at<F>(&mut self, offset: isize, value: T, combine: F)
80 where
81 F: FnOnce(T, T) -> T,
82 {
83 // SAFETY: the copy or initialized caller proves this logical slot.
84 unsafe {
85 let ptr = self.data_mut().as_mut_ptr().offset(offset);
86 ptr.write(combine(ptr.read(), value));
87 }
88 }
89}
90
91impl<'a, T> OverwriteWriter<T> for RawStridedMut<'a, MaybeUninit<T>> {
92 fn dims(&self) -> &[usize] {
93 self.dims()
94 }
95 fn strides(&self) -> &[isize] {
96 self.strides()
97 }
98 fn offset(&self) -> isize {
99 self.offset()
100 }
101 unsafe fn data_ptr(&mut self) -> *mut T {
102 self.data_mut().as_mut_ptr().cast()
103 }
104 unsafe fn write_at(&mut self, offset: isize, value: T) {
105 // SAFETY: the prepared layout validates every logical destination.
106 unsafe {
107 self.data_mut()
108 .as_mut_ptr()
109 .offset(offset)
110 .write(MaybeUninit::new(value))
111 }
112 }
113}
114
115pub(crate) struct InitializedRawDest<'a, T> {
116 ptr: *mut T,
117 extent: usize,
118 dims: &'a [usize],
119 strides: &'a [isize],
120 offset: isize,
121 _marker: PhantomData<&'a mut [MaybeUninit<T>]>,
122}
123
124impl<'a, T> OverwriteWriter<T> for InitializedRawDest<'a, T> {
125 fn dims(&self) -> &[usize] {
126 self.dims
127 }
128 fn strides(&self) -> &[isize] {
129 self.strides
130 }
131 fn offset(&self) -> isize {
132 self.offset
133 }
134 unsafe fn data_ptr(&mut self) -> *mut T {
135 self.ptr
136 }
137 unsafe fn write_at(&mut self, offset: isize, value: T) {
138 debug_assert!(offset >= 0 && (offset as usize) < self.extent);
139 // SAFETY: the copy proof and extent check cover this logical slot.
140 unsafe { self.ptr.offset(offset).write(value) }
141 }
142}
143
144impl<'a, T> ReadModifyWrite<T> for InitializedRawDest<'a, T>
145where
146 T: Add<Output = T>,
147{
148 #[inline(always)]
149 unsafe fn add_at<F>(&mut self, offset: isize, value: T, combine: F)
150 where
151 F: FnOnce(T, T) -> T,
152 {
153 debug_assert!(offset >= 0 && (offset as usize) < self.extent);
154 // SAFETY: the copy proof and extent check cover this logical slot.
155 unsafe {
156 let ptr = self.ptr.offset(offset);
157 ptr.write(combine(ptr.read(), value));
158 }
159 }
160}
161
162/// A compiled copy traversal for one `(dims, dst_strides, src_strides)`
163/// layout pair.
164///
165/// `compile` proves the layout facts once (rank agreement, extent overflow,
166/// destination injectivity) and fuses/orders the loop nest; each `execute*`
167/// call then only re-checks the per-call facts (that the supplied views carry
168/// exactly the compiled layout) before replaying the prepared loops.
169///
170/// Overlapping `src`/`dest` memory is not supported, matching the rest of the
171/// crate.
172///
173/// Ranks above [`RAW_FUSED_RANK_LIMIT`](crate::RAW_FUSED_RANK_LIMIT) are supported by the same fused
174/// traversal, which then stores its axes in one rank-bounded allocation.
175///
176/// # Example
177///
178/// ```rust
179/// use strided_basic::{CopyPlan, RawStridedMut, RawStridedRef};
180///
181/// let dims = [2usize, 3];
182/// let src_strides = [3isize, 1];
183/// let dst_strides = [1isize, 2]; // transposed destination
184/// let plan = CopyPlan::compile(&dims, &dst_strides, &src_strides).unwrap();
185///
186/// let src = [0.0f64, 1.0, 2.0, 10.0, 11.0, 12.0];
187/// let mut dst = [0.0f64; 6];
188/// let src_ref = RawStridedRef::new(&src, &dims, &src_strides, 0).unwrap();
189/// let mut dst_mut = RawStridedMut::new(&mut dst, &dims, &dst_strides, 0).unwrap();
190/// plan.execute(&mut dst_mut, &src_ref).unwrap();
191/// assert_eq!(dst, [0.0, 10.0, 1.0, 11.0, 2.0, 12.0]);
192/// ```
193#[derive(Clone, Debug, Default)]
194pub struct CopyPlan {
195 dims: AxisVec<usize>,
196 dst_strides: AxisVec<isize>,
197 src_strides: AxisVec<isize>,
198 fused: PairTraversal,
199}
200
201impl CopyPlan {
202 /// `None` when the fused traversal is stored in the rank-bounded spill.
203 #[cfg(feature = "parallel")]
204 #[inline]
205 pub(crate) fn fused_layout(&self) -> Option<&FusedPairLayout> {
206 self.fused.inline()
207 }
208
209 pub(crate) fn execute_uninit_then<'a, T, R>(
210 &self,
211 dest: &'a mut RawStridedMut<'a, MaybeUninit<T>>,
212 src: &RawStridedRef<'_, T>,
213 f: impl for<'b> FnOnce(InitializedRawDest<'b, T>) -> R,
214 ) -> Result<R>
215 where
216 T: Copy + MaybeSendSync,
217 {
218 self.execute_uninit(dest, src)?;
219 let data = dest.data_mut();
220 let receipt = InitializedRawDest {
221 ptr: data.as_mut_ptr().cast(),
222 extent: data.len(),
223 dims: dest.dims(),
224 strides: dest.strides(),
225 offset: dest.offset(),
226 _marker: PhantomData,
227 };
228 Ok(f(receipt))
229 }
230
231 /// Compile a copy plan for the given layout pair.
232 ///
233 /// Performs the layout validation and traversal construction
234 /// (fuse + order) once:
235 ///
236 /// - `dims`, `dst_strides`, and `src_strides` must have equal length
237 /// ([`StridedError::StrideLengthMismatch`]);
238 /// - the total element count must not overflow `usize`
239 /// ([`StridedError::OffsetOverflow`]);
240 /// - the destination layout must be injective, i.e. map distinct logical
241 /// indices to distinct offsets
242 /// ([`StridedError::NonInjectiveOutputLayout`]).
243 pub fn compile(dims: &[usize], dst_strides: &[isize], src_strides: &[isize]) -> Result<Self> {
244 if dims.len() != dst_strides.len() || dims.len() != src_strides.len() {
245 return Err(StridedError::StrideLengthMismatch);
246 }
247 // A zero-sized layout has zero elements even when its other extents
248 // overflow; only a nonempty overflowing count is rejected.
249 crate::kernel::total_len(dims)?;
250 if !crate::layout_check::is_injective_layout(dims, dst_strides) {
251 return Err(StridedError::NonInjectiveOutputLayout);
252 }
253 Ok(Self {
254 dims: dims.into(),
255 dst_strides: dst_strides.into(),
256 src_strides: src_strides.into(),
257 fused: fuse_pair_traversal(dims, dst_strides, src_strides),
258 })
259 }
260
261 /// Check the per-call facts: the supplied views must carry exactly the
262 /// compiled layout. Buffer bounds against that layout were already proven
263 /// by [`RawStridedRef::new`]/[`RawStridedMut::new`] (or asserted by the
264 /// caller of the `new_unchecked` constructors), so layout equality is the
265 /// complete precondition for the pointer-based fused replay below.
266 fn check_call<D, S>(
267 &self,
268 dest: &RawStridedMut<'_, D>,
269 src: &RawStridedRef<'_, S>,
270 ) -> Result<()> {
271 check_layout_pair(&self.dims, &self.dst_strides, &self.src_strides, dest, src)
272 }
273
274 /// `dest = src` into a potentially uninitialized destination.
275 ///
276 /// On success every reachable logical destination element is initialized.
277 /// Non-reachable holes in the backing allocation are not written.
278 pub fn execute_uninit<T>(
279 &self,
280 dest: &mut RawStridedMut<'_, MaybeUninit<T>>,
281 src: &RawStridedRef<'_, T>,
282 ) -> Result<()>
283 where
284 T: Copy + MaybeSendSync,
285 {
286 self.check_call(dest, src)?;
287 replay_fused(
288 dest,
289 src,
290 &self.fused,
291 |dst, value| {
292 dst.write(value);
293 },
294 |value| value,
295 );
296 Ok(())
297 }
298
299 /// `dest = src`. Allocation-free for fused ranks at most
300 /// [`RAW_FUSED_RANK_LIMIT`](crate::RAW_FUSED_RANK_LIMIT).
301 pub fn execute<T>(
302 &self,
303 dest: &mut RawStridedMut<'_, T>,
304 src: &RawStridedRef<'_, T>,
305 ) -> Result<()>
306 where
307 T: Copy + MaybeSendSync,
308 {
309 self.check_call(dest, src)?;
310 replay_fused(
311 dest,
312 src,
313 &self.fused,
314 |dst, value| *dst = value,
315 |value: T| value,
316 );
317 Ok(())
318 }
319
320 /// `dest = scale * src`. Allocation-free for fused ranks at most
321 /// [`RAW_FUSED_RANK_LIMIT`](crate::RAW_FUSED_RANK_LIMIT).
322 pub fn execute_scale<T>(
323 &self,
324 dest: &mut RawStridedMut<'_, T>,
325 src: &RawStridedRef<'_, T>,
326 scale: T,
327 ) -> Result<()>
328 where
329 T: Copy + Mul<T, Output = T> + MaybeSendSync,
330 {
331 self.check_call(dest, src)?;
332 replay_fused(
333 dest,
334 src,
335 &self.fused,
336 |dst, value| *dst = value,
337 |value: T| scale * value,
338 );
339 Ok(())
340 }
341
342 /// `dest = conj(src)`. Allocation-free for fused ranks at most
343 /// [`RAW_FUSED_RANK_LIMIT`](crate::RAW_FUSED_RANK_LIMIT).
344 pub fn execute_conj<T>(
345 &self,
346 dest: &mut RawStridedMut<'_, T>,
347 src: &RawStridedRef<'_, T>,
348 ) -> Result<()>
349 where
350 T: Copy + ElementOpApply + MaybeSendSync,
351 {
352 self.check_call(dest, src)?;
353 replay_fused(
354 dest,
355 src,
356 &self.fused,
357 |dst, value| *dst = value,
358 |value: T| value.conj(),
359 );
360 Ok(())
361 }
362}
363
364/// Verify that a pair of raw views carries exactly a compiled layout.
365///
366/// Shared by [`CopyPlan`] and [`UpdatePlan`], whose `compile` methods prove the
367/// layout facts once. Buffer bounds against that layout were already proven by
368/// [`RawStridedRef::new`]/[`RawStridedMut::new`] (or asserted by the caller of
369/// the `new_unchecked` constructors), so layout equality is the complete
370/// per-call precondition for the pointer-based fused replay.
371fn check_layout_pair<D, S>(
372 dims: &[usize],
373 dst_strides: &[isize],
374 src_strides: &[isize],
375 dest: &RawStridedMut<'_, D>,
376 src: &RawStridedRef<'_, S>,
377) -> Result<()> {
378 if dest.dims() != dims
379 || src.dims() != dims
380 || dest.strides() != dst_strides
381 || src.strides() != src_strides
382 {
383 return Err(StridedError::PlanLayoutMismatch);
384 }
385 Ok(())
386}
387
388/// A compiled read-modify-write traversal for one `(dims, dst_strides,
389/// src_strides)` layout pair.
390///
391/// [`execute`](Self::execute) computes `dest = alpha * f(src) + beta * dest` by
392/// replaying the same fused pair traversal as [`CopyPlan`]: `compile` proves
393/// the layout facts once (rank agreement, extent overflow, destination
394/// injectivity) and each call only re-checks that the supplied views carry the
395/// compiled layout before running monomorphized loops. Execute is
396/// allocation-free for fused ranks at most
397/// [`RAW_FUSED_RANK_LIMIT`](crate::RAW_FUSED_RANK_LIMIT), and the injective
398/// destination makes a large replay parallel-safe exactly as in [`CopyPlan`].
399///
400/// Overlapping `src`/`dest` memory is not supported, matching the rest of the
401/// crate.
402///
403/// # Example
404///
405/// ```rust
406/// use strided_basic::{RawStridedMut, RawStridedRef, UpdatePlan};
407///
408/// let dims = [2usize, 2];
409/// let dst_strides = [1isize, 2];
410/// let src_strides = [1isize, 2];
411/// let plan = UpdatePlan::compile(&dims, &dst_strides, &src_strides).unwrap();
412///
413/// let src = [1.0f64, 2.0, 3.0, 4.0];
414/// let mut dst = [10.0f64, 20.0, 30.0, 40.0];
415/// let src_ref = RawStridedRef::new(&src, &dims, &src_strides, 0).unwrap();
416/// let mut dst_mut = RawStridedMut::new(&mut dst, &dims, &dst_strides, 0).unwrap();
417/// // dst = 2 * src + 3 * dst
418/// plan.execute(&mut dst_mut, &src_ref, 2.0, 3.0, |x| x).unwrap();
419/// assert_eq!(dst, [32.0, 64.0, 96.0, 128.0]);
420/// ```
421#[derive(Clone, Debug)]
422pub struct UpdatePlan {
423 dims: AxisVec<usize>,
424 dst_strides: AxisVec<isize>,
425 src_strides: AxisVec<isize>,
426 fused: PairTraversal,
427}
428
429impl UpdatePlan {
430 /// Compile an update plan for the given layout pair.
431 ///
432 /// Performs the same layout validation as [`CopyPlan::compile`]:
433 ///
434 /// - `dims`, `dst_strides`, and `src_strides` must have equal length
435 /// ([`StridedError::StrideLengthMismatch`]);
436 /// - the total element count must not overflow `usize`
437 /// ([`StridedError::OffsetOverflow`]);
438 /// - the destination layout must be injective
439 /// ([`StridedError::NonInjectiveOutputLayout`]), so two logical indices
440 /// never name one slot and a parallel replay writes each slot once.
441 pub fn compile(dims: &[usize], dst_strides: &[isize], src_strides: &[isize]) -> Result<Self> {
442 if dims.len() != dst_strides.len() || dims.len() != src_strides.len() {
443 return Err(StridedError::StrideLengthMismatch);
444 }
445 crate::kernel::total_len(dims)?;
446 if !crate::layout_check::is_injective_layout(dims, dst_strides) {
447 return Err(StridedError::NonInjectiveOutputLayout);
448 }
449 Ok(Self {
450 dims: dims.into(),
451 dst_strides: dst_strides.into(),
452 src_strides: src_strides.into(),
453 fused: fuse_pair_traversal(dims, dst_strides, src_strides),
454 })
455 }
456
457 /// `dest = alpha * f(src) + beta * dest`.
458 ///
459 /// `f` is applied to the source element before scaling. When `beta` is
460 /// zero (`-0.0` included; `NaN` is not zero) the replay is write-only: the
461 /// old destination element is never read, so a NaN-filled or uninitialized
462 /// destination cannot contaminate the result, matching BLAS `beta = 0`
463 /// semantics. Otherwise the old destination element is read through the
464 /// destination layout. Allocation-free for fused ranks at most
465 /// [`RAW_FUSED_RANK_LIMIT`](crate::RAW_FUSED_RANK_LIMIT).
466 ///
467 /// # Errors
468 ///
469 /// [`StridedError::PlanLayoutMismatch`] when the supplied views do not
470 /// carry the compiled layout, before any write.
471 pub fn execute<T, F>(
472 &self,
473 dest: &mut RawStridedMut<'_, T>,
474 src: &RawStridedRef<'_, T>,
475 alpha: T,
476 beta: T,
477 f: F,
478 ) -> Result<()>
479 where
480 T: Copy + MaybeSendSync + Add<Output = T> + Mul<Output = T> + Zero + One + PartialEq,
481 F: Fn(T) -> T + MaybeSendSync,
482 {
483 check_layout_pair(&self.dims, &self.dst_strides, &self.src_strides, dest, src)?;
484 if beta.is_zero() {
485 // Write-only: never form a read of the old destination.
486 if alpha.is_one() {
487 replay_fused(dest, src, &self.fused, |dst, value| *dst = value, f);
488 } else {
489 replay_fused(dest, src, &self.fused, |dst, value| *dst = alpha * value, f);
490 }
491 } else {
492 replay_fused(
493 dest,
494 src,
495 &self.fused,
496 |dst, value| *dst = alpha * value + beta * *dst,
497 f,
498 );
499 }
500 Ok(())
501 }
502
503 /// `dest = alpha * f(src)` into a potentially uninitialized destination.
504 ///
505 /// This is [`execute`](Self::execute) with `beta = 0`: the destination is
506 /// never read. On success every reachable logical destination element is
507 /// initialized; non-reachable holes in the backing allocation are not
508 /// written. Allocation-free for fused ranks at most
509 /// [`RAW_FUSED_RANK_LIMIT`](crate::RAW_FUSED_RANK_LIMIT).
510 ///
511 /// # Errors
512 ///
513 /// [`StridedError::PlanLayoutMismatch`] when the supplied views do not
514 /// carry the compiled layout, before any write.
515 ///
516 /// # Examples
517 ///
518 /// ```rust
519 /// use core::mem::MaybeUninit;
520 /// use strided_basic::{RawStridedMut, RawStridedRef, UpdatePlan};
521 ///
522 /// let dims = [2usize, 2];
523 /// let strides = [1isize, 2];
524 /// let plan = UpdatePlan::compile(&dims, &strides, &strides).unwrap();
525 /// let src = [1.0f64, 2.0, 3.0, 4.0];
526 /// let mut storage = [MaybeUninit::<f64>::uninit(); 4];
527 /// let mut dst = RawStridedMut::new(&mut storage, &dims, &strides, 0).unwrap();
528 /// let source = RawStridedRef::new(&src, &dims, &strides, 0).unwrap();
529 /// // dst = 2 * src
530 /// plan.execute_uninit(&mut dst, &source, 2.0, |x| x).unwrap();
531 /// drop(dst);
532 /// // SAFETY: every reachable destination slot was initialized above.
533 /// let values: Vec<f64> = storage.iter().map(|v| unsafe { v.assume_init() }).collect();
534 /// assert_eq!(values, [2.0, 4.0, 6.0, 8.0]);
535 /// ```
536 pub fn execute_uninit<T, F>(
537 &self,
538 dest: &mut RawStridedMut<'_, MaybeUninit<T>>,
539 src: &RawStridedRef<'_, T>,
540 alpha: T,
541 f: F,
542 ) -> Result<()>
543 where
544 T: Copy + MaybeSendSync + Mul<Output = T> + One + PartialEq,
545 F: Fn(T) -> T + MaybeSendSync,
546 {
547 check_layout_pair(&self.dims, &self.dst_strides, &self.src_strides, dest, src)?;
548 if alpha.is_one() {
549 replay_fused(
550 dest,
551 src,
552 &self.fused,
553 |dst, value| {
554 dst.write(value);
555 },
556 f,
557 );
558 } else {
559 replay_fused(
560 dest,
561 src,
562 &self.fused,
563 |dst, value| {
564 dst.write(alpha * value);
565 },
566 f,
567 );
568 }
569 Ok(())
570 }
571}
572
573/// Replay a compiled fused layout, splitting it across worker threads when the
574/// repository threshold and the active execution policy allow it.
575///
576/// Parallel replay is sound only because [`CopyPlan::compile`] proved the
577/// destination layout injective: disjoint logical ranges then write disjoint
578/// destination slots. The logical index space `0..total` (column-major over
579/// the fused axes) is split into contiguous worker ranges, which divides the
580/// outer fused axes and, when there are fewer outer positions than workers,
581/// also chunks long inner runs (for example a rank-1 contiguous copy).
582fn replay_fused<D, S, Apply, Op>(
583 dest: &mut RawStridedMut<'_, D>,
584 src: &RawStridedRef<'_, S>,
585 traversal: &PairTraversal,
586 apply: Apply,
587 op: Op,
588) where
589 D: Copy + MaybeSendSync,
590 S: Copy + MaybeSendSync,
591 Apply: Fn(&mut D, S) + MaybeSendSync,
592 Op: Fn(S) -> S + MaybeSendSync,
593{
594 let src_ptr = src.data().as_ptr();
595 let src_base = src.offset();
596 let dst_base = dest.offset();
597 let dst_ptr = dest.data_mut().as_mut_ptr();
598 // SAFETY: `check_call` proved both views carry exactly the compiled
599 // layout, and `RawStridedRef`/`RawStridedMut` guarantee every offset
600 // reachable through that layout lies inside their data. `traversal` is the
601 // fusion of that layout, so every logical index in `0..total` is an
602 // in-bounds slot of both. The compiled destination layout is injective
603 // and the exclusive destination borrow cannot overlap the shared source
604 // borrow.
605 unsafe {
606 match traversal {
607 PairTraversal::Inline(layout) => {
608 replay_fused_raw(dst_ptr, dst_base, src_ptr, src_base, layout, apply, op)
609 }
610 PairTraversal::Spilled(axes) => {
611 replay_spilled_raw(dst_ptr, dst_base, src_ptr, src_base, axes, apply, op)
612 }
613 }
614 }
615}
616
617/// Pointer-level fused replay shared by [`CopyPlan`] and the dynamic slice
618/// plans, which replay a fused window layout at a runtime base offset.
619///
620/// Splits `0..total` across workers when the repository threshold and the
621/// active execution policy allow more than one thread, otherwise runs the
622/// serial kernel directly.
623///
624/// # Safety
625///
626/// Every logical index of `layout`, mapped through its destination strides
627/// from `dst_base` and its source strides from `src_base`, must be an
628/// in-bounds slot of the destination and source allocations. The
629/// destination strides must be injective over `layout`, the destination
630/// must not overlap the source, and nothing else may access the destination
631/// slots during the call.
632pub(crate) unsafe fn replay_fused_raw<D, S, Apply, Op>(
633 dst_ptr: *mut D,
634 dst_base: isize,
635 src_ptr: *const S,
636 src_base: isize,
637 layout: &FusedPairLayout,
638 apply: Apply,
639 op: Op,
640) where
641 D: Copy + MaybeSendSync,
642 S: Copy + MaybeSendSync,
643 Apply: Fn(&mut D, S) + MaybeSendSync,
644 Op: Fn(S) -> S + MaybeSendSync,
645{
646 let total = fused_total(layout);
647 if total == 0 {
648 return;
649 }
650 #[cfg(feature = "parallel")]
651 {
652 let nthreads = crate::threading::parallel_threads_for_len(total);
653 if nthreads > 1 {
654 let dst_ptr = crate::threading::SendPtr(dst_ptr);
655 let src_ptr = crate::threading::SendPtr(src_ptr as *mut S);
656 crate::threading::parallel_for_each(0..total, nthreads, &|range| {
657 // SAFETY: the caller contract makes every index in bounds; the
658 // worker ranges are disjoint and the destination strides
659 // injective, so no two workers write the same slot, and the
660 // source is only read.
661 unsafe {
662 apply_fused_range(
663 dst_ptr.as_ptr(),
664 dst_base,
665 src_ptr.as_const(),
666 src_base,
667 layout,
668 range.start,
669 range.len(),
670 &apply,
671 &op,
672 );
673 }
674 });
675 return;
676 }
677 }
678 // SAFETY: forwarded caller contract over the full range `0..total`.
679 unsafe {
680 apply_fused_range(
681 dst_ptr, dst_base, src_ptr, src_base, layout, 0, total, &apply, &op,
682 );
683 }
684}
685
686/// Pointer-level replay of a spilled axis list.
687///
688/// Mirrors [`replay_fused_raw`] including its worker split; kept separate so
689/// that the inline replay keeps the shape it had before spilled layouts
690/// existed.
691///
692/// # Safety
693///
694/// Every logical index of `axes`, mapped through its destination strides from
695/// `dst_base` and its source strides from `src_base`, must be an in-bounds slot
696/// of the destination and source allocations. The destination strides must be
697/// injective over `axes`, the destination must not overlap the source, and
698/// nothing else may access the destination slots during the call.
699pub(crate) unsafe fn replay_spilled_raw<D, S, Apply, Op>(
700 dst_ptr: *mut D,
701 dst_base: isize,
702 src_ptr: *const S,
703 src_base: isize,
704 axes: &[FusedAxis],
705 apply: Apply,
706 op: Op,
707) where
708 D: Copy + MaybeSendSync,
709 S: Copy + MaybeSendSync,
710 Apply: Fn(&mut D, S) + MaybeSendSync,
711 Op: Fn(S) -> S + MaybeSendSync,
712{
713 let total = axes.iter().map(|axis| axis.dim).product::<usize>();
714 if total == 0 {
715 return;
716 }
717 #[cfg(feature = "parallel")]
718 {
719 let nthreads = crate::threading::parallel_threads_for_len(total);
720 if nthreads > 1 {
721 let dst_ptr = crate::threading::SendPtr(dst_ptr);
722 let src_ptr = crate::threading::SendPtr(src_ptr as *mut S);
723 crate::threading::parallel_for_each(0..total, nthreads, &|range| {
724 // SAFETY: the caller contract makes every index in bounds; the
725 // worker ranges are disjoint and the destination strides
726 // injective, so no two workers write the same slot, and the
727 // source is only read.
728 unsafe {
729 apply_spilled_range(
730 dst_ptr.as_ptr(),
731 dst_base,
732 src_ptr.as_const(),
733 src_base,
734 axes,
735 range.start,
736 range.len(),
737 &apply,
738 &op,
739 );
740 }
741 });
742 return;
743 }
744 }
745 // SAFETY: forwarded caller contract over the full range `0..total`.
746 unsafe {
747 apply_spilled_range(
748 dst_ptr, dst_base, src_ptr, src_base, axes, 0, total, &apply, &op,
749 );
750 }
751}
752
753#[cfg(test)]
754#[path = "copy_plan/tests/tests.rs"]
755mod tests;
756
757#[cfg(all(test, feature = "parallel"))]
758#[path = "copy_plan/tests/parallel_tests.rs"]
759mod parallel_tests;