1use crate::ops_view::{axpy, copy_scale};
11use crate::{ElementOpApply, RawStridedMut, RawStridedRef, Result};
12use core::ops::{Add, Mul};
13
14use crate::maybe_sync::MaybeSendSync;
15
16pub const RAW_FUSED_RANK_LIMIT: usize = 8;
18
19#[derive(Clone, Copy, Debug)]
23pub(crate) struct FusedPairLayout {
24 pub(crate) rank: usize,
25 pub(crate) dims: [usize; RAW_FUSED_RANK_LIMIT],
26 pub(crate) dst_strides: [isize; RAW_FUSED_RANK_LIMIT],
27 pub(crate) src_strides: [isize; RAW_FUSED_RANK_LIMIT],
28}
29
30pub(crate) fn fuse_pair_layout(
31 dims: &[usize],
32 dst_strides: &[isize],
33 src_strides: &[isize],
34) -> Option<FusedPairLayout> {
35 if dims.len() > RAW_FUSED_RANK_LIMIT {
36 return None;
37 }
38 let mut layout = FusedPairLayout {
39 rank: 0,
40 dims: [1; RAW_FUSED_RANK_LIMIT],
41 dst_strides: [0; RAW_FUSED_RANK_LIMIT],
42 src_strides: [0; RAW_FUSED_RANK_LIMIT],
43 };
44 for axis in 0..dims.len() {
45 if dims[axis] == 1 {
46 continue;
47 }
48 if dims[axis] == 0 {
49 return Some(FusedPairLayout {
50 rank: 1,
51 dims: [0; RAW_FUSED_RANK_LIMIT],
52 dst_strides: [0; RAW_FUSED_RANK_LIMIT],
53 src_strides: [0; RAW_FUSED_RANK_LIMIT],
54 });
55 }
56 let mut position = layout.rank;
57 while position > 0 && layout.dst_strides[position - 1] > dst_strides[axis] {
58 layout.dims[position] = layout.dims[position - 1];
59 layout.dst_strides[position] = layout.dst_strides[position - 1];
60 layout.src_strides[position] = layout.src_strides[position - 1];
61 position -= 1;
62 }
63 layout.dims[position] = dims[axis];
64 layout.dst_strides[position] = dst_strides[axis];
65 layout.src_strides[position] = src_strides[axis];
66 layout.rank += 1;
67 }
68 if layout.rank == 0 {
69 layout.rank = 1;
70 layout.dims[0] = 1;
71 }
72 let mut fused = 0usize;
73 for axis in 1..layout.rank {
74 let merged = isize::try_from(layout.dims[fused])
77 .ok()
78 .filter(|&extent| {
79 layout.dst_strides[fused].checked_mul(extent) == Some(layout.dst_strides[axis])
80 && layout.src_strides[fused].checked_mul(extent)
81 == Some(layout.src_strides[axis])
82 })
83 .and_then(|_| layout.dims[fused].checked_mul(layout.dims[axis]));
84 if let Some(merged) = merged {
85 layout.dims[fused] = merged;
86 } else {
87 fused += 1;
88 layout.dims[fused] = layout.dims[axis];
89 layout.dst_strides[fused] = layout.dst_strides[axis];
90 layout.src_strides[fused] = layout.src_strides[axis];
91 }
92 }
93 layout.rank = fused + 1;
94 Some(layout)
95}
96
97#[inline]
103pub(crate) fn fused_total(layout: &FusedPairLayout) -> usize {
104 layout.dims[..layout.rank].iter().product()
105}
106
107pub(crate) fn apply_fused_pair<D, S, Apply, Op>(
114 dst: &mut RawStridedMut<'_, D>,
115 src: &RawStridedRef<'_, S>,
116 layout: &FusedPairLayout,
117 apply: Apply,
118 op: Op,
119) where
120 D: Copy,
121 S: Copy,
122 Apply: Fn(&mut D, S),
123 Op: Fn(S) -> S,
124{
125 let total = fused_total(layout);
126 if total == 0 {
127 return;
128 }
129 let src_ptr = src.data().as_ptr();
130 let src_base = src.offset();
131 let dst_base = dst.offset();
132 let dst_ptr = dst.data_mut().as_mut_ptr();
133 unsafe {
140 apply_fused_range(
141 dst_ptr, dst_base, src_ptr, src_base, layout, 0, total, &apply, &op,
142 );
143 }
144}
145
146#[allow(clippy::too_many_arguments)]
164pub(crate) unsafe fn apply_fused_range<D, S, Apply, Op>(
165 dst_ptr: *mut D,
166 dst_base: isize,
167 src_ptr: *const S,
168 src_base: isize,
169 layout: &FusedPairLayout,
170 start: usize,
171 len: usize,
172 apply: &Apply,
173 op: &Op,
174) where
175 D: Copy,
176 S: Copy,
177 Apply: Fn(&mut D, S),
178 Op: Fn(S) -> S,
179{
180 if len == 0 {
181 return;
182 }
183 let rank = layout.rank;
184 let inner_len = layout.dims[0];
185 let inner_dst = layout.dst_strides[0];
186 let inner_src = layout.src_strides[0];
187
188 let mut index = [0usize; RAW_FUSED_RANK_LIMIT];
192 let mut rest = start;
193 let mut dst_outer = dst_base;
194 let mut src_outer = src_base;
195 for axis in 0..rank {
196 let dim = layout.dims[axis];
197 index[axis] = rest % dim;
198 rest /= dim;
199 if axis > 0 {
200 dst_outer += index[axis] as isize * layout.dst_strides[axis];
201 src_outer += index[axis] as isize * layout.src_strides[axis];
202 }
203 }
204 let mut inner_start = index[0];
205 let mut remaining = len;
206 loop {
207 let run = (inner_len - inner_start).min(remaining);
208 let dst_run = dst_outer + inner_start as isize * inner_dst;
209 let src_run = src_outer + inner_start as isize * inner_src;
210 unsafe {
214 apply_run(
215 dst_ptr, dst_run, inner_dst, src_ptr, src_run, inner_src, run, apply, op,
216 )
217 };
218 remaining -= run;
219 if remaining == 0 {
220 return;
221 }
222 inner_start = 0;
223 let mut axis = 1;
230 while axis < rank {
231 if index[axis] + 1 < layout.dims[axis] {
232 index[axis] += 1;
233 dst_outer += layout.dst_strides[axis];
234 src_outer += layout.src_strides[axis];
235 break;
236 }
237 let last = (layout.dims[axis] - 1) as isize;
238 dst_outer -= last * layout.dst_strides[axis];
239 src_outer -= last * layout.src_strides[axis];
240 index[axis] = 0;
241 axis += 1;
242 }
243 }
244}
245
246#[allow(clippy::too_many_arguments)]
254#[inline(always)]
255unsafe fn apply_run<D, S, Apply, Op>(
256 dst_ptr: *mut D,
257 dst_offset: isize,
258 dst_stride: isize,
259 src_ptr: *const S,
260 src_offset: isize,
261 src_stride: isize,
262 len: usize,
263 apply: &Apply,
264 op: &Op,
265) where
266 D: Copy,
267 S: Copy,
268 Apply: Fn(&mut D, S),
269 Op: Fn(S) -> S,
270{
271 if dst_stride == 1 {
272 let dst_run = unsafe { core::slice::from_raw_parts_mut(dst_ptr.offset(dst_offset), len) };
275 match src_stride {
276 1 => {
277 let src_run =
279 unsafe { core::slice::from_raw_parts(src_ptr.offset(src_offset), len) };
280 for (dst, &value) in dst_run.iter_mut().zip(src_run) {
281 apply(dst, op(value));
282 }
283 }
284 -1 => {
285 let src_run = unsafe {
288 core::slice::from_raw_parts(
289 src_ptr.offset(src_offset - (len as isize - 1)),
290 len,
291 )
292 };
293 for (dst, &value) in dst_run.iter_mut().zip(src_run.iter().rev()) {
294 apply(dst, op(value));
295 }
296 }
297 _ => {
298 let src_start = unsafe { src_ptr.offset(src_offset) };
299 for (position, dst) in dst_run.iter_mut().enumerate() {
300 let value = unsafe { *src_start.offset(position as isize * src_stride) };
302 apply(dst, op(value));
303 }
304 }
305 }
306 return;
307 }
308 let dst_start = unsafe { dst_ptr.offset(dst_offset) };
310 let src_start = unsafe { src_ptr.offset(src_offset) };
311 for position in 0..len as isize {
312 unsafe {
314 let value = *src_start.offset(position * src_stride);
315 apply(&mut *dst_start.offset(position * dst_stride), op(value));
316 }
317 }
318}
319
320fn ensure_same_dims(dst: &[usize], src: &[usize]) -> Result<()> {
321 if dst != src {
322 return Err(crate::StridedError::ShapeMismatch(
323 dst.to_vec(),
324 src.to_vec(),
325 ));
326 }
327 crate::kernel::total_len(dst)?;
330 Ok(())
331}
332
333pub fn copy_scale_raw<T>(
335 dest: &mut RawStridedMut<'_, T>,
336 src: &RawStridedRef<'_, T>,
337 scale: T,
338) -> Result<()>
339where
340 T: Copy + Mul<T, Output = T> + ElementOpApply + MaybeSendSync,
341{
342 ensure_same_dims(dest.dims(), src.dims())?;
343 match fuse_pair_layout(dest.dims(), dest.strides(), src.strides()) {
344 Some(layout) => {
345 apply_fused_pair(
346 dest,
347 src,
348 &layout,
349 |dst, value| *dst = value,
350 |value: T| scale * value,
351 );
352 Ok(())
353 }
354 None => copy_scale(&mut dest.as_view_mut(), &src.as_view(), scale),
355 }
356}
357
358pub fn copy_scale_conj_raw<T>(
360 dest: &mut RawStridedMut<'_, T>,
361 src: &RawStridedRef<'_, T>,
362 scale: T,
363) -> Result<()>
364where
365 T: Copy + Mul<T, Output = T> + ElementOpApply + MaybeSendSync,
366{
367 ensure_same_dims(dest.dims(), src.dims())?;
368 match fuse_pair_layout(dest.dims(), dest.strides(), src.strides()) {
369 Some(layout) => {
370 apply_fused_pair(
371 dest,
372 src,
373 &layout,
374 |dst, value| *dst = value,
375 |value: T| scale * value.conj(),
376 );
377 Ok(())
378 }
379 None => copy_scale(&mut dest.as_view_mut(), &src.as_view().conj(), scale),
380 }
381}
382
383pub fn axpy_raw<T>(
385 dest: &mut RawStridedMut<'_, T>,
386 src: &RawStridedRef<'_, T>,
387 alpha: T,
388) -> Result<()>
389where
390 T: Copy + Add<T, Output = T> + Mul<T, Output = T> + ElementOpApply + MaybeSendSync,
391{
392 ensure_same_dims(dest.dims(), src.dims())?;
393 match fuse_pair_layout(dest.dims(), dest.strides(), src.strides()) {
394 Some(layout) => {
395 apply_fused_pair(
396 dest,
397 src,
398 &layout,
399 |dst, value| *dst = *dst + value,
400 |value: T| alpha * value,
401 );
402 Ok(())
403 }
404 None => axpy(&mut dest.as_view_mut(), &src.as_view(), alpha),
405 }
406}
407
408pub fn axpy_conj_raw<T>(
410 dest: &mut RawStridedMut<'_, T>,
411 src: &RawStridedRef<'_, T>,
412 alpha: T,
413) -> Result<()>
414where
415 T: Copy + Add<T, Output = T> + Mul<T, Output = T> + ElementOpApply + MaybeSendSync,
416{
417 ensure_same_dims(dest.dims(), src.dims())?;
418 match fuse_pair_layout(dest.dims(), dest.strides(), src.strides()) {
419 Some(layout) => {
420 apply_fused_pair(
421 dest,
422 src,
423 &layout,
424 |dst, value| *dst = *dst + value,
425 |value: T| alpha * value.conj(),
426 );
427 Ok(())
428 }
429 None => axpy(&mut dest.as_view_mut(), &src.as_view().conj(), alpha),
430 }
431}
432
433#[cfg(test)]
434#[path = "raw_ops/tests/tests.rs"]
435mod tests;