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 empty() -> Self {
64 Self {
65 rank: 0,
66 dims: [1; RAW_FUSED_RANK_LIMIT],
67 dst_strides: [0; RAW_FUSED_RANK_LIMIT],
68 src_strides: [0; RAW_FUSED_RANK_LIMIT],
69 }
70 }
71
72 fn from_axes(axes: &[FusedAxis]) -> Self {
73 debug_assert!(axes.len() <= RAW_FUSED_RANK_LIMIT);
74 let mut layout = Self {
75 rank: axes.len(),
76 dims: [1; RAW_FUSED_RANK_LIMIT],
77 dst_strides: [0; RAW_FUSED_RANK_LIMIT],
78 src_strides: [0; RAW_FUSED_RANK_LIMIT],
79 };
80 for (slot, axis) in layout.dims[..axes.len()].iter_mut().zip(axes) {
81 *slot = axis.dim;
82 }
83 for (slot, axis) in layout.dst_strides[..axes.len()].iter_mut().zip(axes) {
84 *slot = axis.dst;
85 }
86 for (slot, axis) in layout.src_strides[..axes.len()].iter_mut().zip(axes) {
87 *slot = axis.src;
88 }
89 layout
90 }
91}
92
93#[derive(Clone, Debug)]
100pub(crate) enum PairTraversal {
101 Inline(FusedPairLayout),
102 Spilled(Vec<FusedAxis>),
103}
104
105impl Default for FusedPairLayout {
106 fn default() -> Self {
107 Self::empty()
108 }
109}
110
111impl Default for PairTraversal {
112 fn default() -> Self {
113 Self::empty()
114 }
115}
116
117impl PairTraversal {
118 #[inline]
120 fn empty() -> Self {
121 Self::Inline(FusedPairLayout::empty())
122 }
123
124 #[cfg(any(test, feature = "parallel"))]
126 #[inline]
127 pub(crate) fn inline(&self) -> Option<&FusedPairLayout> {
128 match self {
129 Self::Inline(layout) => Some(layout),
130 Self::Spilled(_) => None,
131 }
132 }
133
134 #[cfg(test)]
136 pub(crate) fn rank(&self) -> usize {
137 match self {
138 Self::Inline(layout) => layout.rank,
139 Self::Spilled(axes) => axes.len(),
140 }
141 }
142}
143
144fn fuse_axes(
150 scratch: &mut [FusedAxis],
151 dims: &[usize],
152 dst_strides: &[isize],
153 src_strides: &[isize],
154) -> usize {
155 debug_assert!(scratch.len() >= dims.len());
156 let mut rank = 0usize;
157 for axis in 0..dims.len() {
158 if dims[axis] == 1 {
159 continue;
160 }
161 if dims[axis] == 0 {
162 scratch[0] = FusedAxis {
163 dim: 0,
164 dst: 0,
165 src: 0,
166 };
167 return 1;
168 }
169 let entry = FusedAxis {
170 dim: dims[axis],
171 dst: dst_strides[axis],
172 src: src_strides[axis],
173 };
174 let mut position = rank;
175 while position > 0 && scratch[position - 1].dst > entry.dst {
176 scratch[position] = scratch[position - 1];
177 position -= 1;
178 }
179 scratch[position] = entry;
180 rank += 1;
181 }
182 if rank == 0 {
183 scratch[0] = EMPTY_AXIS;
184 return 1;
185 }
186 let mut fused = 0usize;
187 for axis in 1..rank {
188 let merged = isize::try_from(scratch[fused].dim)
191 .ok()
192 .filter(|&extent| {
193 scratch[fused].dst.checked_mul(extent) == Some(scratch[axis].dst)
194 && scratch[fused].src.checked_mul(extent) == Some(scratch[axis].src)
195 })
196 .and_then(|_| scratch[fused].dim.checked_mul(scratch[axis].dim));
197 if let Some(merged) = merged {
198 scratch[fused].dim = merged;
199 } else {
200 fused += 1;
201 scratch[fused] = scratch[axis];
202 }
203 }
204 fused + 1
205}
206
207pub(crate) fn fuse_pair_traversal(
214 dims: &[usize],
215 dst_strides: &[isize],
216 src_strides: &[isize],
217) -> PairTraversal {
218 if dims.len() <= RAW_FUSED_RANK_LIMIT {
219 let mut scratch = [EMPTY_AXIS; RAW_FUSED_RANK_LIMIT];
220 let rank = fuse_axes(&mut scratch, dims, dst_strides, src_strides);
221 debug_assert!(rank <= RAW_FUSED_RANK_LIMIT);
222 return PairTraversal::Inline(FusedPairLayout::from_axes(&scratch[..rank]));
223 }
224 let mut scratch = vec![EMPTY_AXIS; dims.len()];
225 let rank = fuse_axes(&mut scratch, dims, dst_strides, src_strides);
226 scratch.truncate(rank);
227 if rank <= RAW_FUSED_RANK_LIMIT {
228 PairTraversal::Inline(FusedPairLayout::from_axes(&scratch))
229 } else {
230 PairTraversal::Spilled(scratch)
231 }
232}
233
234pub(crate) fn fuse_pair_layout(
239 dims: &[usize],
240 dst_strides: &[isize],
241 src_strides: &[isize],
242) -> Option<FusedPairLayout> {
243 match fuse_pair_traversal(dims, dst_strides, src_strides) {
244 PairTraversal::Inline(layout) => Some(layout),
245 PairTraversal::Spilled(_) => None,
246 }
247}
248
249#[inline]
255pub(crate) fn fused_total(layout: &FusedPairLayout) -> usize {
256 layout.dims[..layout.rank].iter().product()
257}
258
259#[inline]
261fn spilled_total(axes: &[FusedAxis]) -> usize {
262 axes.iter().map(|axis| axis.dim).product()
263}
264
265pub(crate) fn apply_fused_pair<D, S, Apply, Op>(
272 dst: &mut RawStridedMut<'_, D>,
273 src: &RawStridedRef<'_, S>,
274 layout: &FusedPairLayout,
275 apply: Apply,
276 op: Op,
277) where
278 D: Copy,
279 S: Copy,
280 Apply: Fn(&mut D, S),
281 Op: Fn(S) -> S,
282{
283 let total = fused_total(layout);
284 if total == 0 {
285 return;
286 }
287 let src_ptr = src.data().as_ptr();
288 let src_base = src.offset();
289 let dst_base = dst.offset();
290 let dst_ptr = dst.data_mut().as_mut_ptr();
291 unsafe {
298 apply_fused_range(
299 dst_ptr, dst_base, src_ptr, src_base, layout, 0, total, &apply, &op,
300 );
301 }
302}
303
304#[inline(never)]
312pub(crate) fn apply_spilled_pair<D, S, Apply, Op>(
313 dst: &mut RawStridedMut<'_, D>,
314 src: &RawStridedRef<'_, S>,
315 axes: &[FusedAxis],
316 apply: Apply,
317 op: Op,
318) where
319 D: Copy,
320 S: Copy,
321 Apply: Fn(&mut D, S),
322 Op: Fn(S) -> S,
323{
324 let total = spilled_total(axes);
325 if total == 0 {
326 return;
327 }
328 let src_ptr = src.data().as_ptr();
329 let src_base = src.offset();
330 let dst_base = dst.offset();
331 let dst_ptr = dst.data_mut().as_mut_ptr();
332 unsafe {
335 apply_spilled_range(
336 dst_ptr, dst_base, src_ptr, src_base, axes, 0, total, &apply, &op,
337 );
338 }
339}
340
341#[allow(clippy::too_many_arguments)]
359pub(crate) unsafe fn apply_fused_range<D, S, Apply, Op>(
360 dst_ptr: *mut D,
361 dst_base: isize,
362 src_ptr: *const S,
363 src_base: isize,
364 layout: &FusedPairLayout,
365 start: usize,
366 len: usize,
367 apply: &Apply,
368 op: &Op,
369) where
370 D: Copy,
371 S: Copy,
372 Apply: Fn(&mut D, S),
373 Op: Fn(S) -> S,
374{
375 if len == 0 {
376 return;
377 }
378 let rank = layout.rank;
379 let inner_len = layout.dims[0];
380 let inner_dst = layout.dst_strides[0];
381 let inner_src = layout.src_strides[0];
382
383 let mut index = [0usize; RAW_FUSED_RANK_LIMIT];
387 let mut rest = start;
388 let mut dst_outer = dst_base;
389 let mut src_outer = src_base;
390 for axis in 0..rank {
391 let dim = layout.dims[axis];
392 index[axis] = rest % dim;
393 rest /= dim;
394 if axis > 0 {
395 dst_outer += index[axis] as isize * layout.dst_strides[axis];
396 src_outer += index[axis] as isize * layout.src_strides[axis];
397 }
398 }
399 let mut inner_start = index[0];
400 let mut remaining = len;
401 loop {
402 let run = (inner_len - inner_start).min(remaining);
403 let dst_run = dst_outer + inner_start as isize * inner_dst;
404 let src_run = src_outer + inner_start as isize * inner_src;
405 unsafe {
409 apply_run(
410 dst_ptr, dst_run, inner_dst, src_ptr, src_run, inner_src, run, apply, op,
411 )
412 };
413 remaining -= run;
414 if remaining == 0 {
415 return;
416 }
417 inner_start = 0;
418 let mut axis = 1;
425 while axis < rank {
426 if index[axis] + 1 < layout.dims[axis] {
427 index[axis] += 1;
428 dst_outer += layout.dst_strides[axis];
429 src_outer += layout.src_strides[axis];
430 break;
431 }
432 let last = (layout.dims[axis] - 1) as isize;
433 dst_outer -= last * layout.dst_strides[axis];
434 src_outer -= last * layout.src_strides[axis];
435 index[axis] = 0;
436 axis += 1;
437 }
438 }
439}
440
441#[allow(clippy::too_many_arguments)]
455#[inline(never)]
456pub(crate) unsafe fn apply_spilled_range<D, S, Apply, Op>(
457 dst_ptr: *mut D,
458 dst_base: isize,
459 src_ptr: *const S,
460 src_base: isize,
461 axes: &[FusedAxis],
462 start: usize,
463 len: usize,
464 apply: &Apply,
465 op: &Op,
466) where
467 D: Copy,
468 S: Copy,
469 Apply: Fn(&mut D, S),
470 Op: Fn(S) -> S,
471{
472 if len == 0 {
473 return;
474 }
475 let rank = axes.len();
476 let inner_len = axes[0].dim;
477 let inner_dst = axes[0].dst;
478 let inner_src = axes[0].src;
479
480 let mut index = vec![0usize; rank];
481 let mut rest = start;
482 let mut dst_outer = dst_base;
483 let mut src_outer = src_base;
484 for axis in 0..rank {
485 let dim = axes[axis].dim;
486 index[axis] = rest % dim;
487 rest /= dim;
488 if axis > 0 {
489 dst_outer += index[axis] as isize * axes[axis].dst;
490 src_outer += index[axis] as isize * axes[axis].src;
491 }
492 }
493 let mut inner_start = index[0];
494 let mut remaining = len;
495 loop {
496 let run = (inner_len - inner_start).min(remaining);
497 let dst_run = dst_outer + inner_start as isize * inner_dst;
498 let src_run = src_outer + inner_start as isize * inner_src;
499 unsafe {
503 apply_run(
504 dst_ptr, dst_run, inner_dst, src_ptr, src_run, inner_src, run, apply, op,
505 )
506 };
507 remaining -= run;
508 if remaining == 0 {
509 return;
510 }
511 inner_start = 0;
512 let mut axis = 1;
515 while axis < rank {
516 if index[axis] + 1 < axes[axis].dim {
517 index[axis] += 1;
518 dst_outer += axes[axis].dst;
519 src_outer += axes[axis].src;
520 break;
521 }
522 let last = (axes[axis].dim - 1) as isize;
523 dst_outer -= last * axes[axis].dst;
524 src_outer -= last * axes[axis].src;
525 index[axis] = 0;
526 axis += 1;
527 }
528 }
529}
530
531#[allow(clippy::too_many_arguments)]
539#[inline(always)]
540unsafe fn apply_run<D, S, Apply, Op>(
541 dst_ptr: *mut D,
542 dst_offset: isize,
543 dst_stride: isize,
544 src_ptr: *const S,
545 src_offset: isize,
546 src_stride: isize,
547 len: usize,
548 apply: &Apply,
549 op: &Op,
550) where
551 D: Copy,
552 S: Copy,
553 Apply: Fn(&mut D, S),
554 Op: Fn(S) -> S,
555{
556 if dst_stride == 1 {
557 let dst_run = unsafe { core::slice::from_raw_parts_mut(dst_ptr.offset(dst_offset), len) };
560 match src_stride {
561 1 => {
562 let src_run =
564 unsafe { core::slice::from_raw_parts(src_ptr.offset(src_offset), len) };
565 for (dst, &value) in dst_run.iter_mut().zip(src_run) {
566 apply(dst, op(value));
567 }
568 }
569 -1 => {
570 let src_run = unsafe {
573 core::slice::from_raw_parts(
574 src_ptr.offset(src_offset - (len as isize - 1)),
575 len,
576 )
577 };
578 for (dst, &value) in dst_run.iter_mut().zip(src_run.iter().rev()) {
579 apply(dst, op(value));
580 }
581 }
582 _ => {
583 let src_start = unsafe { src_ptr.offset(src_offset) };
584 for (position, dst) in dst_run.iter_mut().enumerate() {
585 let value = unsafe { *src_start.offset(position as isize * src_stride) };
587 apply(dst, op(value));
588 }
589 }
590 }
591 return;
592 }
593 let dst_start = unsafe { dst_ptr.offset(dst_offset) };
595 let src_start = unsafe { src_ptr.offset(src_offset) };
596 for position in 0..len as isize {
597 unsafe {
599 let value = *src_start.offset(position * src_stride);
600 apply(&mut *dst_start.offset(position * dst_stride), op(value));
601 }
602 }
603}
604
605fn ensure_same_dims(dst: &[usize], src: &[usize]) -> Result<()> {
606 if dst != src {
607 return Err(crate::StridedError::ShapeMismatch(
608 dst.to_vec(),
609 src.to_vec(),
610 ));
611 }
612 crate::kernel::total_len(dst)?;
615 Ok(())
616}
617
618pub fn copy_scale_raw<T>(
620 dest: &mut RawStridedMut<'_, T>,
621 src: &RawStridedRef<'_, T>,
622 scale: T,
623) -> Result<()>
624where
625 T: Copy + Mul<T, Output = T> + ElementOpApply + MaybeSendSync,
626{
627 ensure_same_dims(dest.dims(), src.dims())?;
628 match fuse_pair_traversal(dest.dims(), dest.strides(), src.strides()) {
629 PairTraversal::Inline(layout) => apply_fused_pair(
630 dest,
631 src,
632 &layout,
633 |dst, value| *dst = value,
634 |value: T| scale * value,
635 ),
636 PairTraversal::Spilled(axes) => apply_spilled_pair(
637 dest,
638 src,
639 &axes,
640 |dst, value| *dst = value,
641 |value: T| scale * value,
642 ),
643 }
644 Ok(())
645}
646
647pub fn copy_scale_conj_raw<T>(
649 dest: &mut RawStridedMut<'_, T>,
650 src: &RawStridedRef<'_, T>,
651 scale: T,
652) -> Result<()>
653where
654 T: Copy + Mul<T, Output = T> + ElementOpApply + MaybeSendSync,
655{
656 ensure_same_dims(dest.dims(), src.dims())?;
657 match fuse_pair_traversal(dest.dims(), dest.strides(), src.strides()) {
658 PairTraversal::Inline(layout) => apply_fused_pair(
659 dest,
660 src,
661 &layout,
662 |dst, value| *dst = value,
663 |value: T| scale * value.conj(),
664 ),
665 PairTraversal::Spilled(axes) => apply_spilled_pair(
666 dest,
667 src,
668 &axes,
669 |dst, value| *dst = value,
670 |value: T| scale * value.conj(),
671 ),
672 }
673 Ok(())
674}
675
676pub fn axpy_raw<T>(
678 dest: &mut RawStridedMut<'_, T>,
679 src: &RawStridedRef<'_, T>,
680 alpha: T,
681) -> Result<()>
682where
683 T: Copy + Add<T, Output = T> + Mul<T, Output = T> + ElementOpApply + MaybeSendSync,
684{
685 ensure_same_dims(dest.dims(), src.dims())?;
686 match fuse_pair_traversal(dest.dims(), dest.strides(), src.strides()) {
687 PairTraversal::Inline(layout) => apply_fused_pair(
688 dest,
689 src,
690 &layout,
691 |dst, value| *dst = *dst + value,
692 |value: T| alpha * value,
693 ),
694 PairTraversal::Spilled(axes) => apply_spilled_pair(
695 dest,
696 src,
697 &axes,
698 |dst, value| *dst = *dst + value,
699 |value: T| alpha * value,
700 ),
701 }
702 Ok(())
703}
704
705pub fn axpy_conj_raw<T>(
707 dest: &mut RawStridedMut<'_, T>,
708 src: &RawStridedRef<'_, T>,
709 alpha: T,
710) -> Result<()>
711where
712 T: Copy + Add<T, Output = T> + Mul<T, Output = T> + ElementOpApply + MaybeSendSync,
713{
714 ensure_same_dims(dest.dims(), src.dims())?;
715 match fuse_pair_traversal(dest.dims(), dest.strides(), src.strides()) {
716 PairTraversal::Inline(layout) => apply_fused_pair(
717 dest,
718 src,
719 &layout,
720 |dst, value| *dst = *dst + value,
721 |value: T| alpha * value.conj(),
722 ),
723 PairTraversal::Spilled(axes) => apply_spilled_pair(
724 dest,
725 src,
726 &axes,
727 |dst, value| *dst = *dst + value,
728 |value: T| alpha * value.conj(),
729 ),
730 }
731 Ok(())
732}
733
734#[cfg(test)]
735#[path = "raw_ops/tests/tests.rs"]
736mod tests;