1use crate::ScalarBase;
8use std::any::{Any, TypeId};
9use std::cell::RefCell;
10use std::collections::HashMap;
11use strided_view::{StridedArray, StridedView, StridedViewMut};
12
13pub struct ContiguousOperand<T: Copy + 'static> {
15 ptr: *const T,
16 row_stride: isize,
17 col_stride: isize,
18 batch_strides: Vec<isize>,
19 conj: bool,
20 pub(crate) _buf: Option<StridedArray<T>>,
22 buf_is_pooled: bool,
23}
24
25pub struct ContiguousOperandMut<T: Copy + 'static> {
27 ptr: *mut T,
28 row_stride: isize,
29 col_stride: isize,
30 batch_strides: Vec<isize>,
31 needs_writeback: bool,
34 pub(crate) _buf: Option<StridedArray<T>>,
36 buf_is_pooled: bool,
37}
38
39thread_local! {
40 static BUFFER_POOL: RefCell<HashMap<TypeId, Box<dyn Any>>> = RefCell::new(HashMap::new());
41}
42
43const MAX_POOL_PER_TYPE: usize = 16;
44const MAX_POOLED_BYTES: usize = 64 * 1024 * 1024;
45
46fn take_pooled_vec_uninit<T: Copy + 'static>(len: usize) -> Vec<T> {
47 BUFFER_POOL.with(|pool| {
48 let mut pool = pool.borrow_mut();
49 let entry = pool
50 .entry(TypeId::of::<T>())
51 .or_insert_with(|| Box::new(Vec::<Vec<T>>::new()));
52 let vecs = entry
53 .downcast_mut::<Vec<Vec<T>>>()
54 .expect("buffer pool type mismatch");
55
56 let mut best_idx = None;
57 let mut best_cap = usize::MAX;
58 for (idx, v) in vecs.iter().enumerate() {
59 let cap = v.capacity();
60 if cap >= len && cap < best_cap {
61 best_idx = Some(idx);
62 best_cap = cap;
63 }
64 }
65
66 let mut data = best_idx
67 .map(|idx| vecs.swap_remove(idx))
68 .unwrap_or_else(|| Vec::with_capacity(len));
69 if data.capacity() < len {
70 data.reserve(len - data.capacity());
71 }
72 unsafe { data.set_len(len) };
73 data
74 })
75}
76
77fn return_pooled_vec<T: Copy + 'static>(mut data: Vec<T>) {
78 let bytes = data.capacity().saturating_mul(std::mem::size_of::<T>());
79 if bytes == 0 || bytes > MAX_POOLED_BYTES {
80 return;
81 }
82 data.clear();
83 BUFFER_POOL.with(|pool| {
84 let mut pool = pool.borrow_mut();
85 let entry = pool
86 .entry(TypeId::of::<T>())
87 .or_insert_with(|| Box::new(Vec::<Vec<T>>::new()));
88 let vecs = entry
89 .downcast_mut::<Vec<Vec<T>>>()
90 .expect("buffer pool type mismatch");
91 if vecs.len() >= MAX_POOL_PER_TYPE {
92 if let Some((min_idx, min_cap)) = vecs
93 .iter()
94 .enumerate()
95 .map(|(i, v)| (i, v.capacity()))
96 .min_by_key(|(_, cap)| *cap)
97 {
98 if min_cap < data.capacity() {
99 vecs.swap_remove(min_idx);
100 vecs.push(data);
101 }
102 }
103 } else {
104 vecs.push(data);
105 }
106 });
107}
108
109fn alloc_col_major_uninit_with_pool<T: Copy + 'static>(dims: &[usize]) -> (StridedArray<T>, bool) {
110 let total: usize = dims.iter().product::<usize>().max(1);
111 let bytes = total.saturating_mul(std::mem::size_of::<T>());
112 if bytes == 0 || bytes > MAX_POOLED_BYTES {
113 return (alloc_col_major_uninit(dims), false);
114 }
115 let data = take_pooled_vec_uninit::<T>(total);
116 let arr = unsafe { StridedArray::col_major_from_buffer_uninit(data, dims) };
117 (arr, true)
118}
119
120fn alloc_maybe_pooled<T: Copy + 'static>(
122 dims: &[usize],
123 use_pool: bool,
124) -> (StridedArray<T>, bool) {
125 if use_pool {
126 alloc_col_major_uninit_with_pool(dims)
127 } else {
128 (alloc_col_major_uninit(dims), false)
129 }
130}
131
132#[cfg(test)]
133fn pooled_count_for_type<T: 'static>() -> usize {
134 BUFFER_POOL.with(|pool| {
135 let mut pool = pool.borrow_mut();
136 let Some(entry) = pool.get_mut(&TypeId::of::<T>()) else {
137 return 0;
138 };
139 entry
140 .downcast_mut::<Vec<Vec<T>>>()
141 .map_or(0, |vecs| vecs.len())
142 })
143}
144
145impl<T: Copy + 'static> ContiguousOperand<T> {
146 #[inline]
148 pub fn ptr(&self) -> *const T {
149 self.ptr
150 }
151
152 #[inline]
154 pub fn row_stride(&self) -> isize {
155 self.row_stride
156 }
157
158 #[inline]
160 pub fn col_stride(&self) -> isize {
161 self.col_stride
162 }
163
164 #[inline]
166 pub fn batch_strides(&self) -> &[isize] {
167 &self.batch_strides
168 }
169
170 #[inline]
172 pub fn conj(&self) -> bool {
173 self.conj
174 }
175
176 #[cfg(test)]
178 #[inline]
179 pub(crate) fn has_buf(&self) -> bool {
180 self._buf.is_some()
181 }
182}
183
184impl<T: Copy + 'static> ContiguousOperandMut<T> {
185 #[inline]
187 pub fn ptr(&self) -> *mut T {
188 self.ptr
189 }
190
191 #[inline]
193 pub fn row_stride(&self) -> isize {
194 self.row_stride
195 }
196
197 #[inline]
199 pub fn col_stride(&self) -> isize {
200 self.col_stride
201 }
202
203 #[inline]
205 pub fn batch_strides(&self) -> &[isize] {
206 &self.batch_strides
207 }
208
209 #[cfg(test)]
211 #[inline]
212 pub(crate) fn has_buf(&self) -> bool {
213 self._buf.is_some()
214 }
215
216 #[cfg(test)]
219 #[inline]
220 pub(crate) fn needs_writeback(&self) -> bool {
221 self.needs_writeback
222 }
223}
224
225impl<T: Copy + Send + Sync> ContiguousOperandMut<T> {
226 pub fn finalize_into(self, dest: &mut StridedViewMut<T>) -> crate::Result<()> {
231 if self.needs_writeback {
232 if let Some(ref buf) = self._buf {
233 strided_perm::copy_into(dest, &buf.view())?;
234 }
235 }
236 Ok(())
237 }
238}
239
240impl<T: Copy + 'static> Drop for ContiguousOperand<T> {
241 fn drop(&mut self) {
242 if self.buf_is_pooled {
243 if let Some(arr) = self._buf.take() {
244 return_pooled_vec(arr.into_data());
245 }
246 }
247 }
248}
249
250impl<T: Copy + 'static> Drop for ContiguousOperandMut<T> {
251 fn drop(&mut self) {
252 if self.buf_is_pooled {
253 if let Some(arr) = self._buf.take() {
254 return_pooled_vec(arr.into_data());
255 }
256 }
257 }
258}
259
260struct ContiguityCheck {
262 fused_g1: Option<(usize, isize)>,
263 fused_g2: Option<(usize, isize)>,
264 needs_copy: bool,
265}
266
267fn try_fuse_col_major_group(dims: &[usize], strides: &[isize]) -> Option<(usize, isize)> {
271 if dims.len() != strides.len() {
272 return None;
273 }
274 let total = dims
275 .iter()
276 .try_fold(1usize, |acc, &dim| acc.checked_mul(dim))?;
277 if dims.is_empty() {
278 return Some((1, 0));
279 }
280
281 let mut base_stride = None;
282 let mut expected_stride = None;
283 for (&dim, &stride) in dims.iter().zip(strides.iter()) {
284 if dim <= 1 {
285 continue;
286 }
287 if stride == 0 {
288 return None;
289 }
290 if let Some(expected) = expected_stride {
291 if stride != expected {
292 return None;
293 }
294 } else {
295 base_stride = Some(stride);
296 }
297 let dim = isize::try_from(dim).ok()?;
298 expected_stride = Some(stride.checked_mul(dim)?);
299 }
300
301 let stride = base_stride.unwrap_or_else(|| {
302 strides
303 .iter()
304 .copied()
305 .min_by_key(|stride| stride.unsigned_abs())
306 .unwrap_or(0)
307 });
308 Some((total, stride))
309}
310
311fn check_contiguity(
317 group1_dims: &[usize],
318 group1_strides: &[isize],
319 group2_dims: &[usize],
320 group2_strides: &[isize],
321 requires_unit_stride: bool,
322) -> ContiguityCheck {
323 let fused_g1 = try_fuse_col_major_group(group1_dims, group1_strides);
324 let fused_g2 = try_fuse_col_major_group(group2_dims, group2_strides);
325
326 let mut needs_copy = fused_g1.is_none() || fused_g2.is_none();
327
328 if requires_unit_stride && !needs_copy {
329 let (_, rs) = fused_g1.unwrap();
330 let (_, cs) = fused_g2.unwrap();
331 if rs != 0 && rs != 1 && cs != 0 && cs != 1 {
332 needs_copy = true;
333 }
334 }
335
336 ContiguityCheck {
337 fused_g1,
338 fused_g2,
339 needs_copy,
340 }
341}
342
343fn col_major_layout(
347 buf: &StridedArray<impl Copy>,
348 n_group1: usize,
349 n_inner: usize,
350) -> (isize, isize, Vec<isize>) {
351 let m: usize = buf.dims()[..n_group1].iter().product::<usize>().max(1);
352 let row_stride = if m == 0 { 0 } else { 1isize };
353 let col_stride = m as isize;
354 let batch_strides = buf.strides()[n_inner..].to_vec();
355 (row_stride, col_stride, batch_strides)
356}
357
358pub(crate) fn alloc_col_major_uninit<T: Copy>(dims: &[usize]) -> StridedArray<T> {
364 let total: usize = dims.iter().product::<usize>().max(1);
365 let mut data = Vec::with_capacity(total);
370 unsafe { data.set_len(total) };
371
372 let mut strides = vec![0isize; dims.len()];
375 if !dims.is_empty() {
376 strides[0] = 1;
377 for i in 1..dims.len() {
378 strides[i] = strides[i - 1] * dims[i - 1] as isize;
379 }
380 }
381
382 let arr = StridedArray::from_parts(data, dims, &strides, 0).expect("col-major allocation");
383 arr
384}
385
386pub fn prepare_input_view<T: ScalarBase + 'static>(
397 view: &StridedView<T>,
398 n_group1: usize,
399 n_group2: usize,
400 conj: bool,
401 requires_unit_stride: bool,
402 use_pool: bool,
403 materialize_conj_fn: Option<fn(T) -> T>,
404) -> crate::Result<ContiguousOperand<T>> {
405 let dims = view.dims();
406 let strides = view.strides();
407 let n_inner = n_group1 + n_group2;
408
409 if let Some(conj_fn) = materialize_conj_fn {
412 if conj {
413 let (mut buf, buf_is_pooled) = alloc_maybe_pooled(dims, use_pool);
414 strided_kernel::map_into(&mut buf.view_mut(), view, conj_fn)?;
415 let ptr = buf.view().ptr();
416 let (row_stride, col_stride, batch_strides) = col_major_layout(&buf, n_group1, n_inner);
417 return Ok(ContiguousOperand {
418 ptr,
419 row_stride,
420 col_stride,
421 batch_strides,
422 conj: false,
423 _buf: Some(buf),
424 buf_is_pooled,
425 });
426 }
427 }
428
429 let check = check_contiguity(
430 &dims[..n_group1],
431 &strides[..n_group1],
432 &dims[n_group1..n_inner],
433 &strides[n_group1..n_inner],
434 requires_unit_stride,
435 );
436
437 if check.needs_copy {
438 let (mut buf, buf_is_pooled) = alloc_maybe_pooled(dims, use_pool);
439 strided_kernel::copy_into_col_major(&mut buf.view_mut(), view)?;
440 let ptr = buf.view().ptr();
441 let (row_stride, col_stride, batch_strides) = col_major_layout(&buf, n_group1, n_inner);
442 Ok(ContiguousOperand {
443 ptr,
444 row_stride,
445 col_stride,
446 batch_strides,
447 conj,
448 _buf: Some(buf),
449 buf_is_pooled,
450 })
451 } else {
452 let (_, rs) = check.fused_g1.unwrap();
453 let (_, cs) = check.fused_g2.unwrap();
454 Ok(ContiguousOperand {
455 ptr: view.ptr(),
456 row_stride: rs,
457 col_stride: cs,
458 batch_strides: strides[n_inner..].to_vec(),
459 conj,
460 _buf: None,
461 buf_is_pooled: false,
462 })
463 }
464}
465
466pub fn prepare_input_owned<T: ScalarBase + 'static>(
474 arr: StridedArray<T>,
475 n_group1: usize,
476 n_group2: usize,
477 conj: bool,
478 requires_unit_stride: bool,
479 use_pool: bool,
480 materialize_conj_fn: Option<fn(T) -> T>,
481) -> crate::Result<ContiguousOperand<T>> {
482 let dims = arr.dims().to_vec();
483 let strides = arr.strides().to_vec();
484 let n_inner = n_group1 + n_group2;
485
486 if let Some(conj_fn) = materialize_conj_fn {
489 if conj {
490 let (mut buf, buf_is_pooled) = alloc_maybe_pooled(&dims, use_pool);
491 strided_kernel::map_into(&mut buf.view_mut(), &arr.view(), conj_fn)?;
492 let ptr = buf.view().ptr();
493 let (row_stride, col_stride, batch_strides) = col_major_layout(&buf, n_group1, n_inner);
494 return Ok(ContiguousOperand {
495 ptr,
496 row_stride,
497 col_stride,
498 batch_strides,
499 conj: false,
500 _buf: Some(buf),
501 buf_is_pooled,
502 });
503 }
504 }
505
506 let check = check_contiguity(
507 &dims[..n_group1],
508 &strides[..n_group1],
509 &dims[n_group1..n_inner],
510 &strides[n_group1..n_inner],
511 requires_unit_stride,
512 );
513
514 if check.needs_copy {
515 let (mut buf, buf_is_pooled) = alloc_maybe_pooled(&dims, use_pool);
516 strided_kernel::copy_into_col_major(&mut buf.view_mut(), &arr.view())?;
517 let ptr = buf.view().ptr();
518 let (row_stride, col_stride, batch_strides) = col_major_layout(&buf, n_group1, n_inner);
519 Ok(ContiguousOperand {
520 ptr,
521 row_stride,
522 col_stride,
523 batch_strides,
524 conj,
525 _buf: Some(buf),
526 buf_is_pooled,
527 })
528 } else {
529 let (_, rs) = check.fused_g1.unwrap();
530 let (_, cs) = check.fused_g2.unwrap();
531 let ptr = arr.view().ptr();
532 Ok(ContiguousOperand {
533 ptr,
534 row_stride: rs,
535 col_stride: cs,
536 batch_strides: strides[n_inner..].to_vec(),
537 conj,
538 _buf: Some(arr),
539 buf_is_pooled: false,
540 })
541 }
542}
543
544pub fn prepare_output_view<T: ScalarBase + 'static>(
560 view: &mut StridedViewMut<T>,
561 n_group1: usize,
562 n_group2: usize,
563 beta: T,
564 requires_unit_stride: bool,
565 use_pool: bool,
566) -> crate::Result<ContiguousOperandMut<T>> {
567 let dims = view.dims().to_vec();
568 let strides = view.strides().to_vec();
569 let n_inner = n_group1 + n_group2;
570
571 let check = check_contiguity(
572 &dims[..n_group1],
573 &strides[..n_group1],
574 &dims[n_group1..n_inner],
575 &strides[n_group1..n_inner],
576 requires_unit_stride,
577 );
578
579 if check.needs_copy {
580 let (mut buf, buf_is_pooled) = alloc_maybe_pooled(&dims, use_pool);
581 if beta != T::zero() {
582 strided_kernel::copy_into_col_major(&mut buf.view_mut(), &view.as_view())?;
583 }
584 let ptr = buf.view_mut().as_mut_ptr();
585 let (row_stride, col_stride, batch_strides) = col_major_layout(&buf, n_group1, n_inner);
586 Ok(ContiguousOperandMut {
587 ptr,
588 row_stride,
589 col_stride,
590 batch_strides,
591 needs_writeback: true,
592 _buf: Some(buf),
593 buf_is_pooled,
594 })
595 } else {
596 let (_, rs) = check.fused_g1.unwrap();
597 let (_, cs) = check.fused_g2.unwrap();
598 Ok(ContiguousOperandMut {
599 ptr: view.as_mut_ptr(),
600 row_stride: rs,
601 col_stride: cs,
602 batch_strides: strides[n_inner..].to_vec(),
603 needs_writeback: false,
604 _buf: None,
605 buf_is_pooled: false,
606 })
607 }
608}
609
610#[cfg(test)]
611mod tests_generic_backend {
612 use super::*;
613 use crate::backend::{Backend, NaiveBackend};
614
615 #[test]
616 fn test_input_for_backend_contiguous() {
617 let a = StridedArray::<f64>::col_major(&[2, 3]);
618 let view = a.view();
619 let op = prepare_input_view(
620 &view,
621 1,
622 1,
623 false,
624 <NaiveBackend as Backend<f64>>::REQUIRES_UNIT_STRIDE,
625 false,
626 None,
627 )
628 .unwrap();
629 assert!(op._buf.is_none());
630 assert_eq!(op.row_stride(), 1);
631 assert_eq!(op.col_stride(), 2);
632 assert!(!op.conj());
633 }
634
635 #[test]
636 fn test_input_for_backend_non_contiguous() {
637 let data = vec![0.0f64; 100];
638 let a = StridedArray::<f64>::from_parts(data, &[2, 3, 4], &[20, 4, 1], 0).unwrap();
639 let view = a.view();
640 let op = prepare_input_view(
641 &view,
642 2,
643 1,
644 false,
645 <NaiveBackend as Backend<f64>>::REQUIRES_UNIT_STRIDE,
646 false,
647 None,
648 )
649 .unwrap();
650 assert!(op._buf.is_some());
651 assert_eq!(op.row_stride(), 1);
652 assert_eq!(op.col_stride(), 6);
653 }
654
655 #[test]
656 fn test_output_for_backend_contiguous() {
657 let mut c = StridedArray::<f64>::col_major(&[2, 3]);
658 let mut view = c.view_mut();
659 let op = prepare_output_view(
660 &mut view,
661 1,
662 1,
663 0.0,
664 <NaiveBackend as Backend<f64>>::REQUIRES_UNIT_STRIDE,
665 false,
666 )
667 .unwrap();
668 assert!(!op.needs_writeback);
669 assert!(op._buf.is_none());
670 assert_eq!(op.row_stride(), 1);
671 assert_eq!(op.col_stride(), 2);
672 }
673
674 #[test]
675 fn test_output_for_backend_non_contiguous_beta_zero() {
676 let data = vec![0.0f64; 100];
677 let mut c = StridedArray::<f64>::from_parts(data, &[2, 3, 4], &[20, 4, 1], 0).unwrap();
678 let mut view = c.view_mut();
679 let op = prepare_output_view(
680 &mut view,
681 2,
682 1,
683 0.0,
684 <NaiveBackend as Backend<f64>>::REQUIRES_UNIT_STRIDE,
685 false,
686 )
687 .unwrap();
688 assert!(op.needs_writeback);
689 assert!(op._buf.is_some());
690 assert_eq!(op.row_stride(), 1);
691 assert_eq!(op.col_stride(), 6);
692 }
693
694 #[test]
695 fn test_output_for_backend_non_contiguous_beta_nonzero_and_finalize() {
696 let mut data = vec![0.0f64; 30];
697 data[0] = 10.0;
698 data[1] = 20.0;
699 data[10] = 40.0;
700 let mut c = StridedArray::<f64>::from_parts(data, &[2, 3, 1], &[10, 1, 1], 0).unwrap();
701 let mut view = c.view_mut();
702 let op = prepare_output_view(
703 &mut view,
704 2,
705 1,
706 1.0,
707 <NaiveBackend as Backend<f64>>::REQUIRES_UNIT_STRIDE,
708 false,
709 )
710 .unwrap();
711 assert!(op.needs_writeback);
712 let buf = op._buf.as_ref().unwrap();
713 assert_eq!(buf.get(&[0, 0, 0]), 10.0);
714 assert_eq!(buf.get(&[0, 1, 0]), 20.0);
715 assert_eq!(buf.get(&[1, 0, 0]), 40.0);
716 op.finalize_into(&mut view).unwrap();
717 }
718}
719
720#[cfg(test)]
721mod tests {
722 use super::*;
723 use crate::backend::{ActiveBackend, Backend};
724
725 const UNIT_STRIDE: bool = <ActiveBackend as Backend<f64>>::REQUIRES_UNIT_STRIDE;
727
728 #[test]
729 fn test_borrowed_contiguous_no_copy() {
730 let a = StridedArray::<f64>::col_major(&[2, 3]);
731 let view = a.view();
732
733 let op = prepare_input_view(&view, 1, 1, false, UNIT_STRIDE, true, None).unwrap();
734
735 assert!(!op.has_buf());
736 assert_eq!(op.row_stride(), 1);
737 assert_eq!(op.col_stride(), 2);
738 assert!(!op.conj());
739 }
740
741 #[test]
742 fn test_borrowed_transposed_matrix_no_copy() {
743 let data = vec![0.0f64; 6];
744 let a_t = StridedArray::<f64>::from_parts(data, &[2, 3], &[3, 1], 0).unwrap();
745 let view = a_t.view();
746
747 let op = prepare_input_view(&view, 1, 1, false, UNIT_STRIDE, true, None).unwrap();
748
749 assert!(!op.has_buf());
750 assert_eq!(op.row_stride(), 3);
751 assert_eq!(op.col_stride(), 1);
752 }
753
754 #[test]
755 fn test_borrowed_batched_transposed_matrix_no_copy() {
756 let data = vec![0.0f64; 2 * 3 * 5];
757 let a_t = StridedArray::<f64>::from_parts(data, &[2, 3, 5], &[3, 1, 6], 0).unwrap();
758 let view = a_t.view();
759
760 let op = prepare_input_view(&view, 1, 1, false, UNIT_STRIDE, true, None).unwrap();
761
762 assert!(!op.has_buf());
763 assert_eq!(op.row_stride(), 3);
764 assert_eq!(op.col_stride(), 1);
765 assert_eq!(op.batch_strides(), &[6]);
766 }
767
768 #[test]
769 fn test_borrowed_non_contiguous_copies() {
770 let data = vec![0.0f64; 100];
771 let a = StridedArray::<f64>::from_parts(data, &[2, 3, 4], &[20, 4, 1], 0).unwrap();
772 let view = a.view();
773
774 let op = prepare_input_view(&view, 2, 1, false, UNIT_STRIDE, true, None).unwrap();
775
776 assert!(op.has_buf());
777 assert_eq!(op.row_stride(), 1);
778 assert_eq!(op.col_stride(), 6);
779 }
780
781 #[test]
782 fn test_owned_contiguous_no_copy() {
783 let a = StridedArray::<f64>::col_major(&[2, 3]);
784
785 let op = prepare_input_owned(a, 1, 1, false, UNIT_STRIDE, true, None).unwrap();
786
787 assert!(op.has_buf());
788 assert_eq!(op.row_stride(), 1);
789 assert_eq!(op.col_stride(), 2);
790 }
791
792 #[test]
793 fn test_owned_non_contiguous_copies() {
794 let data = vec![0.0f64; 100];
795 let a = StridedArray::<f64>::from_parts(data, &[2, 3, 4], &[20, 4, 1], 0).unwrap();
796
797 let op = prepare_input_owned(a, 2, 1, false, UNIT_STRIDE, true, None).unwrap();
798
799 assert!(op.has_buf());
800 assert_eq!(op.row_stride(), 1);
801 assert_eq!(op.col_stride(), 6);
802 }
803
804 #[test]
805 fn test_output_view_contiguous() {
806 let mut c = StridedArray::<f64>::col_major(&[2, 3]);
807 let mut view = c.view_mut();
808
809 let op = prepare_output_view(&mut view, 1, 1, 0.0, UNIT_STRIDE, true).unwrap();
810
811 assert!(!op.needs_writeback());
812 assert!(!op.has_buf());
813 assert_eq!(op.row_stride(), 1);
814 assert_eq!(op.col_stride(), 2);
815 }
816
817 #[test]
818 fn test_output_view_non_contiguous_beta_zero() {
819 let data = vec![0.0f64; 100];
820 let mut c = StridedArray::<f64>::from_parts(data, &[2, 3, 4], &[20, 4, 1], 0).unwrap();
821 let mut view = c.view_mut();
822
823 let op = prepare_output_view(&mut view, 2, 1, 0.0, UNIT_STRIDE, true).unwrap();
824
825 assert!(op.needs_writeback());
826 assert!(op.has_buf());
827 assert_eq!(op.row_stride(), 1);
828 assert_eq!(op.col_stride(), 6);
829 }
830
831 #[test]
832 fn test_output_view_non_contiguous_beta_nonzero_and_finalize() {
833 let mut data = vec![0.0f64; 30];
834 data[0] = 10.0;
835 data[1] = 20.0;
836 data[2] = 30.0;
837 data[10] = 40.0;
838 data[11] = 50.0;
839 data[12] = 60.0;
840 let mut c = StridedArray::<f64>::from_parts(data, &[2, 3, 1], &[10, 1, 1], 0).unwrap();
841
842 assert_eq!(c.get(&[0, 0, 0]), 10.0);
843 assert_eq!(c.get(&[1, 1, 0]), 50.0);
844
845 let mut view = c.view_mut();
846
847 let mut op = prepare_output_view(&mut view, 2, 1, 1.0, UNIT_STRIDE, true).unwrap();
848
849 assert!(op.needs_writeback());
850 assert!(op.has_buf());
851
852 let buf = op._buf.as_ref().unwrap();
853 assert_eq!(buf.get(&[0, 0, 0]), 10.0);
854 assert_eq!(buf.get(&[1, 1, 0]), 50.0);
855
856 {
857 let result_data = vec![100.0f64; 6];
858 let result =
859 StridedArray::<f64>::from_parts(result_data, &[2, 3, 1], &[3, 1, 1], 0).unwrap();
860 strided_kernel::copy_into(&mut op._buf.as_mut().unwrap().view_mut(), &result.view())
861 .unwrap();
862 op.ptr = op._buf.as_mut().unwrap().view_mut().as_mut_ptr();
863 }
864
865 op.finalize_into(&mut view).unwrap();
866
867 assert_eq!(c.get(&[0, 0, 0]), 100.0);
868 assert_eq!(c.get(&[0, 1, 0]), 100.0);
869 assert_eq!(c.get(&[0, 2, 0]), 100.0);
870 assert_eq!(c.get(&[1, 0, 0]), 100.0);
871 assert_eq!(c.get(&[1, 1, 0]), 100.0);
872 assert_eq!(c.get(&[1, 2, 0]), 100.0);
873 }
874
875 #[test]
876 fn test_prepare_input_view_temp_buffer_is_recycled() {
877 let before = pooled_count_for_type::<f64>();
878 let data = vec![0.0f64; 100];
879 let a = StridedArray::<f64>::from_parts(data, &[2, 3, 4], &[20, 4, 1], 0).unwrap();
880 let view = a.view();
881
882 {
883 let op = prepare_input_view(&view, 2, 1, false, UNIT_STRIDE, true, None).unwrap();
884 assert!(op.has_buf());
885 }
886
887 let after = pooled_count_for_type::<f64>();
888 assert!(after >= before.saturating_add(1));
889 }
890}