1use crate::{ElementOpApply, RawStridedMut, RawStridedRef, Result};
13use core::ops::{Add, Mul};
14
15use crate::maybe_sync::MaybeSendSync;
16
17pub const RAW_FUSED_RANK_LIMIT: usize = 8;
28
29#[derive(Clone, Copy, Debug)]
32pub(crate) struct FusedAxis {
33 pub(crate) dim: usize,
34 pub(crate) dst: isize,
35 pub(crate) src: isize,
36}
37
38const EMPTY_AXIS: FusedAxis = FusedAxis {
39 dim: 1,
40 dst: 0,
41 src: 0,
42};
43
44#[derive(Clone, Copy, Debug)]
51pub(crate) struct FusedPairLayout {
52 pub(crate) rank: usize,
53 pub(crate) dims: [usize; RAW_FUSED_RANK_LIMIT],
54 dst_strides: [isize; RAW_FUSED_RANK_LIMIT],
55 src_strides: [isize; RAW_FUSED_RANK_LIMIT],
56}
57
58impl FusedPairLayout {
59 fn from_axes(axes: &[FusedAxis]) -> Self {
60 debug_assert!(axes.len() <= RAW_FUSED_RANK_LIMIT);
61 let mut layout = Self {
62 rank: axes.len(),
63 dims: [1; RAW_FUSED_RANK_LIMIT],
64 dst_strides: [0; RAW_FUSED_RANK_LIMIT],
65 src_strides: [0; RAW_FUSED_RANK_LIMIT],
66 };
67 for (slot, axis) in layout.dims[..axes.len()].iter_mut().zip(axes) {
68 *slot = axis.dim;
69 }
70 for (slot, axis) in layout.dst_strides[..axes.len()].iter_mut().zip(axes) {
71 *slot = axis.dst;
72 }
73 for (slot, axis) in layout.src_strides[..axes.len()].iter_mut().zip(axes) {
74 *slot = axis.src;
75 }
76 layout
77 }
78}
79
80#[derive(Clone, Debug)]
87pub(crate) enum PairTraversal {
88 Inline(FusedPairLayout),
89 Spilled(Vec<FusedAxis>),
90}
91
92impl PairTraversal {
93 #[cfg(any(test, feature = "parallel"))]
95 #[inline]
96 pub(crate) fn inline(&self) -> Option<&FusedPairLayout> {
97 match self {
98 Self::Inline(layout) => Some(layout),
99 Self::Spilled(_) => None,
100 }
101 }
102
103 #[cfg(test)]
105 pub(crate) fn rank(&self) -> usize {
106 match self {
107 Self::Inline(layout) => layout.rank,
108 Self::Spilled(axes) => axes.len(),
109 }
110 }
111}
112
113fn fuse_axes(
119 scratch: &mut [FusedAxis],
120 dims: &[usize],
121 dst_strides: &[isize],
122 src_strides: &[isize],
123) -> usize {
124 debug_assert!(scratch.len() >= dims.len());
125 let mut rank = 0usize;
126 for axis in 0..dims.len() {
127 if dims[axis] == 1 {
128 continue;
129 }
130 if dims[axis] == 0 {
131 scratch[0] = FusedAxis {
132 dim: 0,
133 dst: 0,
134 src: 0,
135 };
136 return 1;
137 }
138 let entry = FusedAxis {
139 dim: dims[axis],
140 dst: dst_strides[axis],
141 src: src_strides[axis],
142 };
143 let mut position = rank;
144 while position > 0 && scratch[position - 1].dst > entry.dst {
145 scratch[position] = scratch[position - 1];
146 position -= 1;
147 }
148 scratch[position] = entry;
149 rank += 1;
150 }
151 if rank == 0 {
152 scratch[0] = EMPTY_AXIS;
153 return 1;
154 }
155 let mut fused = 0usize;
156 for axis in 1..rank {
157 let merged = isize::try_from(scratch[fused].dim)
160 .ok()
161 .filter(|&extent| {
162 scratch[fused].dst.checked_mul(extent) == Some(scratch[axis].dst)
163 && scratch[fused].src.checked_mul(extent) == Some(scratch[axis].src)
164 })
165 .and_then(|_| scratch[fused].dim.checked_mul(scratch[axis].dim));
166 if let Some(merged) = merged {
167 scratch[fused].dim = merged;
168 } else {
169 fused += 1;
170 scratch[fused] = scratch[axis];
171 }
172 }
173 fused + 1
174}
175
176pub(crate) fn fuse_pair_traversal(
183 dims: &[usize],
184 dst_strides: &[isize],
185 src_strides: &[isize],
186) -> PairTraversal {
187 if dims.len() <= RAW_FUSED_RANK_LIMIT {
188 let mut scratch = [EMPTY_AXIS; RAW_FUSED_RANK_LIMIT];
189 let rank = fuse_axes(&mut scratch, dims, dst_strides, src_strides);
190 debug_assert!(rank <= RAW_FUSED_RANK_LIMIT);
191 return PairTraversal::Inline(FusedPairLayout::from_axes(&scratch[..rank]));
192 }
193 let mut scratch = vec![EMPTY_AXIS; dims.len()];
194 let rank = fuse_axes(&mut scratch, dims, dst_strides, src_strides);
195 scratch.truncate(rank);
196 if rank <= RAW_FUSED_RANK_LIMIT {
197 PairTraversal::Inline(FusedPairLayout::from_axes(&scratch))
198 } else {
199 PairTraversal::Spilled(scratch)
200 }
201}
202
203pub(crate) fn fuse_pair_layout(
208 dims: &[usize],
209 dst_strides: &[isize],
210 src_strides: &[isize],
211) -> Option<FusedPairLayout> {
212 match fuse_pair_traversal(dims, dst_strides, src_strides) {
213 PairTraversal::Inline(layout) => Some(layout),
214 PairTraversal::Spilled(_) => None,
215 }
216}
217
218#[inline]
224pub(crate) fn fused_total(layout: &FusedPairLayout) -> usize {
225 layout.dims[..layout.rank].iter().product()
226}
227
228#[inline]
230fn spilled_total(axes: &[FusedAxis]) -> usize {
231 axes.iter().map(|axis| axis.dim).product()
232}
233
234pub(crate) fn apply_fused_pair<D, S, Apply, Op>(
241 dst: &mut RawStridedMut<'_, D>,
242 src: &RawStridedRef<'_, S>,
243 layout: &FusedPairLayout,
244 apply: Apply,
245 op: Op,
246) where
247 D: Copy,
248 S: Copy,
249 Apply: Fn(&mut D, S),
250 Op: Fn(S) -> S,
251{
252 let total = fused_total(layout);
253 if total == 0 {
254 return;
255 }
256 let src_ptr = src.data().as_ptr();
257 let src_base = src.offset();
258 let dst_base = dst.offset();
259 let dst_ptr = dst.data_mut().as_mut_ptr();
260 unsafe {
267 apply_fused_range(
268 dst_ptr, dst_base, src_ptr, src_base, layout, 0, total, &apply, &op,
269 );
270 }
271}
272
273#[inline(never)]
281pub(crate) fn apply_spilled_pair<D, S, Apply, Op>(
282 dst: &mut RawStridedMut<'_, D>,
283 src: &RawStridedRef<'_, S>,
284 axes: &[FusedAxis],
285 apply: Apply,
286 op: Op,
287) where
288 D: Copy,
289 S: Copy,
290 Apply: Fn(&mut D, S),
291 Op: Fn(S) -> S,
292{
293 let total = spilled_total(axes);
294 if total == 0 {
295 return;
296 }
297 let src_ptr = src.data().as_ptr();
298 let src_base = src.offset();
299 let dst_base = dst.offset();
300 let dst_ptr = dst.data_mut().as_mut_ptr();
301 unsafe {
304 apply_spilled_range(
305 dst_ptr, dst_base, src_ptr, src_base, axes, 0, total, &apply, &op,
306 );
307 }
308}
309
310#[allow(clippy::too_many_arguments)]
328pub(crate) unsafe fn apply_fused_range<D, S, Apply, Op>(
329 dst_ptr: *mut D,
330 dst_base: isize,
331 src_ptr: *const S,
332 src_base: isize,
333 layout: &FusedPairLayout,
334 start: usize,
335 len: usize,
336 apply: &Apply,
337 op: &Op,
338) where
339 D: Copy,
340 S: Copy,
341 Apply: Fn(&mut D, S),
342 Op: Fn(S) -> S,
343{
344 if len == 0 {
345 return;
346 }
347 let rank = layout.rank;
348 let inner_len = layout.dims[0];
349 let inner_dst = layout.dst_strides[0];
350 let inner_src = layout.src_strides[0];
351
352 let mut index = [0usize; RAW_FUSED_RANK_LIMIT];
356 let mut rest = start;
357 let mut dst_outer = dst_base;
358 let mut src_outer = src_base;
359 for axis in 0..rank {
360 let dim = layout.dims[axis];
361 index[axis] = rest % dim;
362 rest /= dim;
363 if axis > 0 {
364 dst_outer += index[axis] as isize * layout.dst_strides[axis];
365 src_outer += index[axis] as isize * layout.src_strides[axis];
366 }
367 }
368 let mut inner_start = index[0];
369 let mut remaining = len;
370 loop {
371 let run = (inner_len - inner_start).min(remaining);
372 let dst_run = dst_outer + inner_start as isize * inner_dst;
373 let src_run = src_outer + inner_start as isize * inner_src;
374 unsafe {
378 apply_run(
379 dst_ptr, dst_run, inner_dst, src_ptr, src_run, inner_src, run, apply, op,
380 )
381 };
382 remaining -= run;
383 if remaining == 0 {
384 return;
385 }
386 inner_start = 0;
387 let mut axis = 1;
394 while axis < rank {
395 if index[axis] + 1 < layout.dims[axis] {
396 index[axis] += 1;
397 dst_outer += layout.dst_strides[axis];
398 src_outer += layout.src_strides[axis];
399 break;
400 }
401 let last = (layout.dims[axis] - 1) as isize;
402 dst_outer -= last * layout.dst_strides[axis];
403 src_outer -= last * layout.src_strides[axis];
404 index[axis] = 0;
405 axis += 1;
406 }
407 }
408}
409
410#[allow(clippy::too_many_arguments)]
424#[inline(never)]
425pub(crate) unsafe fn apply_spilled_range<D, S, Apply, Op>(
426 dst_ptr: *mut D,
427 dst_base: isize,
428 src_ptr: *const S,
429 src_base: isize,
430 axes: &[FusedAxis],
431 start: usize,
432 len: usize,
433 apply: &Apply,
434 op: &Op,
435) where
436 D: Copy,
437 S: Copy,
438 Apply: Fn(&mut D, S),
439 Op: Fn(S) -> S,
440{
441 if len == 0 {
442 return;
443 }
444 let rank = axes.len();
445 let inner_len = axes[0].dim;
446 let inner_dst = axes[0].dst;
447 let inner_src = axes[0].src;
448
449 let mut index = vec![0usize; rank];
450 let mut rest = start;
451 let mut dst_outer = dst_base;
452 let mut src_outer = src_base;
453 for axis in 0..rank {
454 let dim = axes[axis].dim;
455 index[axis] = rest % dim;
456 rest /= dim;
457 if axis > 0 {
458 dst_outer += index[axis] as isize * axes[axis].dst;
459 src_outer += index[axis] as isize * axes[axis].src;
460 }
461 }
462 let mut inner_start = index[0];
463 let mut remaining = len;
464 loop {
465 let run = (inner_len - inner_start).min(remaining);
466 let dst_run = dst_outer + inner_start as isize * inner_dst;
467 let src_run = src_outer + inner_start as isize * inner_src;
468 unsafe {
472 apply_run(
473 dst_ptr, dst_run, inner_dst, src_ptr, src_run, inner_src, run, apply, op,
474 )
475 };
476 remaining -= run;
477 if remaining == 0 {
478 return;
479 }
480 inner_start = 0;
481 let mut axis = 1;
484 while axis < rank {
485 if index[axis] + 1 < axes[axis].dim {
486 index[axis] += 1;
487 dst_outer += axes[axis].dst;
488 src_outer += axes[axis].src;
489 break;
490 }
491 let last = (axes[axis].dim - 1) as isize;
492 dst_outer -= last * axes[axis].dst;
493 src_outer -= last * axes[axis].src;
494 index[axis] = 0;
495 axis += 1;
496 }
497 }
498}
499
500#[allow(clippy::too_many_arguments)]
508#[inline(always)]
509unsafe fn apply_run<D, S, Apply, Op>(
510 dst_ptr: *mut D,
511 dst_offset: isize,
512 dst_stride: isize,
513 src_ptr: *const S,
514 src_offset: isize,
515 src_stride: isize,
516 len: usize,
517 apply: &Apply,
518 op: &Op,
519) where
520 D: Copy,
521 S: Copy,
522 Apply: Fn(&mut D, S),
523 Op: Fn(S) -> S,
524{
525 if dst_stride == 1 {
526 let dst_run = unsafe { core::slice::from_raw_parts_mut(dst_ptr.offset(dst_offset), len) };
529 match src_stride {
530 1 => {
531 let src_run =
533 unsafe { core::slice::from_raw_parts(src_ptr.offset(src_offset), len) };
534 for (dst, &value) in dst_run.iter_mut().zip(src_run) {
535 apply(dst, op(value));
536 }
537 }
538 -1 => {
539 let src_run = unsafe {
542 core::slice::from_raw_parts(
543 src_ptr.offset(src_offset - (len as isize - 1)),
544 len,
545 )
546 };
547 for (dst, &value) in dst_run.iter_mut().zip(src_run.iter().rev()) {
548 apply(dst, op(value));
549 }
550 }
551 _ => {
552 let src_start = unsafe { src_ptr.offset(src_offset) };
553 for (position, dst) in dst_run.iter_mut().enumerate() {
554 let value = unsafe { *src_start.offset(position as isize * src_stride) };
556 apply(dst, op(value));
557 }
558 }
559 }
560 return;
561 }
562 let dst_start = unsafe { dst_ptr.offset(dst_offset) };
564 let src_start = unsafe { src_ptr.offset(src_offset) };
565 for position in 0..len as isize {
566 unsafe {
568 let value = *src_start.offset(position * src_stride);
569 apply(&mut *dst_start.offset(position * dst_stride), op(value));
570 }
571 }
572}
573
574fn ensure_same_dims(dst: &[usize], src: &[usize]) -> Result<()> {
575 if dst != src {
576 return Err(crate::StridedError::ShapeMismatch(
577 dst.to_vec(),
578 src.to_vec(),
579 ));
580 }
581 crate::kernel::total_len(dst)?;
584 Ok(())
585}
586
587pub fn copy_scale_raw<T>(
589 dest: &mut RawStridedMut<'_, T>,
590 src: &RawStridedRef<'_, T>,
591 scale: T,
592) -> Result<()>
593where
594 T: Copy + Mul<T, Output = T> + ElementOpApply + MaybeSendSync,
595{
596 ensure_same_dims(dest.dims(), src.dims())?;
597 match fuse_pair_traversal(dest.dims(), dest.strides(), src.strides()) {
598 PairTraversal::Inline(layout) => apply_fused_pair(
599 dest,
600 src,
601 &layout,
602 |dst, value| *dst = value,
603 |value: T| scale * value,
604 ),
605 PairTraversal::Spilled(axes) => apply_spilled_pair(
606 dest,
607 src,
608 &axes,
609 |dst, value| *dst = value,
610 |value: T| scale * value,
611 ),
612 }
613 Ok(())
614}
615
616pub fn copy_scale_conj_raw<T>(
618 dest: &mut RawStridedMut<'_, T>,
619 src: &RawStridedRef<'_, T>,
620 scale: T,
621) -> Result<()>
622where
623 T: Copy + Mul<T, Output = T> + ElementOpApply + MaybeSendSync,
624{
625 ensure_same_dims(dest.dims(), src.dims())?;
626 match fuse_pair_traversal(dest.dims(), dest.strides(), src.strides()) {
627 PairTraversal::Inline(layout) => apply_fused_pair(
628 dest,
629 src,
630 &layout,
631 |dst, value| *dst = value,
632 |value: T| scale * value.conj(),
633 ),
634 PairTraversal::Spilled(axes) => apply_spilled_pair(
635 dest,
636 src,
637 &axes,
638 |dst, value| *dst = value,
639 |value: T| scale * value.conj(),
640 ),
641 }
642 Ok(())
643}
644
645pub fn axpy_raw<T>(
647 dest: &mut RawStridedMut<'_, T>,
648 src: &RawStridedRef<'_, T>,
649 alpha: T,
650) -> Result<()>
651where
652 T: Copy + Add<T, Output = T> + Mul<T, Output = T> + ElementOpApply + MaybeSendSync,
653{
654 ensure_same_dims(dest.dims(), src.dims())?;
655 match fuse_pair_traversal(dest.dims(), dest.strides(), src.strides()) {
656 PairTraversal::Inline(layout) => apply_fused_pair(
657 dest,
658 src,
659 &layout,
660 |dst, value| *dst = *dst + value,
661 |value: T| alpha * value,
662 ),
663 PairTraversal::Spilled(axes) => apply_spilled_pair(
664 dest,
665 src,
666 &axes,
667 |dst, value| *dst = *dst + value,
668 |value: T| alpha * value,
669 ),
670 }
671 Ok(())
672}
673
674pub fn axpy_conj_raw<T>(
676 dest: &mut RawStridedMut<'_, T>,
677 src: &RawStridedRef<'_, T>,
678 alpha: T,
679) -> Result<()>
680where
681 T: Copy + Add<T, Output = T> + Mul<T, Output = T> + ElementOpApply + MaybeSendSync,
682{
683 ensure_same_dims(dest.dims(), src.dims())?;
684 match fuse_pair_traversal(dest.dims(), dest.strides(), src.strides()) {
685 PairTraversal::Inline(layout) => apply_fused_pair(
686 dest,
687 src,
688 &layout,
689 |dst, value| *dst = *dst + value,
690 |value: T| alpha * value.conj(),
691 ),
692 PairTraversal::Spilled(axes) => apply_spilled_pair(
693 dest,
694 src,
695 &axes,
696 |dst, value| *dst = *dst + value,
697 |value: T| alpha * value.conj(),
698 ),
699 }
700 Ok(())
701}
702
703#[cfg(test)]
704#[path = "raw_ops/tests/tests.rs"]
705mod tests;