1use crate::kernel::{
22 build_plan_fused, build_plan_fused_small, ensure_same_shape, for_each_inner_block_preordered,
23 sequential_contiguous_layout, total_len, SMALL_TENSOR_THRESHOLD,
24};
25use crate::layout_check::is_injective_layout;
26use crate::maybe_sync::{MaybeSendSync, MaybeSync};
27use crate::simd;
28use crate::view::{StridedView, StridedViewMut};
29use crate::{Result, StridedError};
30use strided_view::ElementOp;
31
32#[cfg(feature = "parallel")]
33use crate::fuse::compute_costs;
34#[cfg(feature = "parallel")]
35use crate::threading::{for_each_inner_block_with_offsets, mapreduce_threaded, MINTHREADLENGTH};
36
37#[cfg(feature = "parallel")]
39type Body<'a> = &'a (dyn Fn(&[isize], usize, &[isize]) + Sync);
40#[cfg(not(feature = "parallel"))]
41type Body<'a> = &'a dyn Fn(&[isize], usize, &[isize]);
42
43struct Raw<T>(*mut T);
46impl<T> Clone for Raw<T> {
47 fn clone(&self) -> Self {
48 *self
49 }
50}
51impl<T> Copy for Raw<T> {}
52unsafe impl<T> Send for Raw<T> {}
54unsafe impl<T> Sync for Raw<T> {}
55impl<T> Raw<T> {
56 fn get(self) -> *mut T {
58 self.0
59 }
60}
61
62fn validate_destination(dims: &[usize], strides: &[isize]) -> Result<()> {
63 if is_injective_layout(dims, strides) {
64 Ok(())
65 } else {
66 Err(StridedError::NonInjectiveOutputLayout)
67 }
68}
69
70fn run_update(
82 dims: &[usize],
83 strides_list: &[&[isize]],
84 elem_size: usize,
85 body: Body<'_>,
86) -> Result<()> {
87 let total = total_len(dims)?;
88 if total == 0 {
89 return Ok(());
90 }
91 if sequential_contiguous_layout(dims, strides_list)?.is_some() {
96 let zeros: crate::inline::OperandVec<isize> = strides_list.iter().map(|_| 0isize).collect();
97 let ones: crate::inline::OperandVec<isize> = strides_list.iter().map(|_| 1isize).collect();
98 body(&zeros, total, &ones);
99 return Ok(());
100 }
101 if dims.iter().all(|&d| d == 1) {
103 let zeros: crate::inline::OperandVec<isize> = strides_list.iter().map(|_| 0isize).collect();
104 body(&zeros, 1, &zeros);
105 return Ok(());
106 }
107 let (fused_dims, ordered_strides, plan) = if total <= SMALL_TENSOR_THRESHOLD {
109 build_plan_fused_small(dims, strides_list)
110 } else {
111 build_plan_fused(dims, strides_list, Some(0), elem_size)
112 };
113
114 #[cfg(feature = "parallel")]
115 {
116 let total = total_len(&fused_dims)?;
117 let nthreads = crate::execution_policy::rayon_threads();
118 if total > MINTHREADLENGTH && nthreads > 1 {
119 let costs = compute_costs(&ordered_strides);
120 let initial_offsets: crate::inline::OperandVec<isize> =
121 strides_list.iter().map(|_| 0isize).collect();
122 return mapreduce_threaded(
123 &fused_dims,
124 &plan.block,
125 &ordered_strides,
126 &initial_offsets,
127 &costs,
128 nthreads,
129 0,
130 1,
131 &|dims, blocks, strides_list, offsets| {
132 for_each_inner_block_with_offsets(
133 dims,
134 blocks,
135 strides_list,
136 offsets,
137 |offsets, len, strides| {
138 body(offsets, len, strides);
139 Ok(())
140 },
141 )
142 },
143 );
144 }
145 }
146
147 let initial_offsets: crate::inline::OperandVec<isize> =
148 ordered_strides.iter().map(|_| 0isize).collect();
149 for_each_inner_block_preordered(
150 &fused_dims,
151 &plan.block,
152 &ordered_strides,
153 &initial_offsets,
154 |offsets, len, strides| {
155 body(offsets, len, strides);
156 Ok(())
157 },
158 )
159}
160
161#[inline(always)]
168unsafe fn inner_loop_update1<D: Copy, OpD: ElementOp<D>>(
169 dp: *mut D,
170 ds: isize,
171 len: usize,
172 f: &impl Fn(D) -> D,
173) {
174 if ds == 1 {
175 let dst = std::slice::from_raw_parts_mut(dp, len);
176 simd::dispatch_if_large(len, || {
177 for d in dst.iter_mut() {
178 *d = f(OpD::apply(*d));
179 }
180 });
181 } else {
182 let mut dp = dp;
183 for _ in 0..len {
184 *dp = f(OpD::apply(*dp));
185 dp = dp.wrapping_offset(ds);
186 }
187 }
188}
189
190#[inline(always)]
196unsafe fn inner_loop_update2<D: Copy, A: Copy, OpD: ElementOp<D>, OpA: ElementOp<A>>(
197 dp: *mut D,
198 ds: isize,
199 ap: *const A,
200 a_s: isize,
201 len: usize,
202 f: &impl Fn(D, A) -> D,
203) {
204 if ds == 1 && a_s == 1 {
205 let src_a = std::slice::from_raw_parts(ap, len);
206 let dst = std::slice::from_raw_parts_mut(dp, len);
207 simd::dispatch_if_large(len, || {
208 for (d, &a) in dst.iter_mut().zip(src_a) {
209 *d = f(OpD::apply(*d), OpA::apply(a));
210 }
211 });
212 } else {
213 let (mut dp, mut ap) = (dp, ap);
214 for _ in 0..len {
215 *dp = f(OpD::apply(*dp), OpA::apply(*ap));
216 dp = dp.wrapping_offset(ds);
217 ap = ap.wrapping_offset(a_s);
218 }
219 }
220}
221
222#[inline(always)]
227#[allow(clippy::too_many_arguments)] unsafe fn inner_loop_update3<
229 D: Copy,
230 A: Copy,
231 B: Copy,
232 OpD: ElementOp<D>,
233 OpA: ElementOp<A>,
234 OpB: ElementOp<B>,
235>(
236 dp: *mut D,
237 ds: isize,
238 ap: *const A,
239 a_s: isize,
240 bp: *const B,
241 b_s: isize,
242 len: usize,
243 f: &impl Fn(D, A, B) -> D,
244) {
245 if ds == 1 && a_s == 1 && b_s == 1 {
246 let src_a = std::slice::from_raw_parts(ap, len);
247 let src_b = std::slice::from_raw_parts(bp, len);
248 let dst = std::slice::from_raw_parts_mut(dp, len);
249 simd::dispatch_if_large(len, || {
250 for ((d, &a), &b) in dst.iter_mut().zip(src_a).zip(src_b) {
251 *d = f(OpD::apply(*d), OpA::apply(a), OpB::apply(b));
252 }
253 });
254 } else {
255 let (mut dp, mut ap, mut bp) = (dp, ap, bp);
256 for _ in 0..len {
257 *dp = f(OpD::apply(*dp), OpA::apply(*ap), OpB::apply(*bp));
258 dp = dp.wrapping_offset(ds);
259 ap = ap.wrapping_offset(a_s);
260 bp = bp.wrapping_offset(b_s);
261 }
262 }
263}
264
265pub fn map_update_into<D, OpD>(
283 dest: &mut StridedViewMut<D>,
284 f: impl Fn(D) -> D + MaybeSync,
285) -> Result<()>
286where
287 D: Copy + MaybeSendSync,
288 OpD: ElementOp<D>,
289{
290 validate_destination(dest.dims(), dest.strides())?;
291 let dp = Raw(dest.as_mut_ptr());
292 run_update(
293 dest.dims(),
294 &[dest.strides()],
295 std::mem::size_of::<D>(),
296 &|offsets, len, strides| {
297 unsafe {
299 inner_loop_update1::<D, OpD>(dp.get().offset(offsets[0]), strides[0], len, &f);
300 }
301 },
302 )
303}
304
305pub fn zip_update2_into<D, A, OpD, OpA>(
326 dest: &mut StridedViewMut<D>,
327 a: &StridedView<A, OpA>,
328 f: impl Fn(D, A) -> D + MaybeSync,
329) -> Result<()>
330where
331 D: Copy + MaybeSendSync,
332 A: Copy + MaybeSendSync,
333 OpD: ElementOp<D>,
334 OpA: ElementOp<A>,
335{
336 ensure_same_shape(dest.dims(), a.dims())?;
337 validate_destination(dest.dims(), dest.strides())?;
338 let dp = Raw(dest.as_mut_ptr());
339 let ap = Raw(a.ptr() as *mut A);
340 run_update(
341 dest.dims(),
342 &[dest.strides(), a.strides()],
343 std::mem::size_of::<D>().max(std::mem::size_of::<A>()),
344 &|offsets, len, strides| {
345 unsafe {
347 inner_loop_update2::<D, A, OpD, OpA>(
348 dp.get().offset(offsets[0]),
349 strides[0],
350 ap.get().offset(offsets[1]).cast_const(),
351 strides[1],
352 len,
353 &f,
354 );
355 }
356 },
357 )
358}
359
360pub fn zip_update3_into<D, A, B, OpD, OpA, OpB>(
384 dest: &mut StridedViewMut<D>,
385 a: &StridedView<A, OpA>,
386 b: &StridedView<B, OpB>,
387 f: impl Fn(D, A, B) -> D + MaybeSync,
388) -> Result<()>
389where
390 D: Copy + MaybeSendSync,
391 A: Copy + MaybeSendSync,
392 B: Copy + MaybeSendSync,
393 OpD: ElementOp<D>,
394 OpA: ElementOp<A>,
395 OpB: ElementOp<B>,
396{
397 ensure_same_shape(dest.dims(), a.dims())?;
398 ensure_same_shape(dest.dims(), b.dims())?;
399 validate_destination(dest.dims(), dest.strides())?;
400 let dp = Raw(dest.as_mut_ptr());
401 let ap = Raw(a.ptr() as *mut A);
402 let bp = Raw(b.ptr() as *mut B);
403 run_update(
404 dest.dims(),
405 &[dest.strides(), a.strides(), b.strides()],
406 std::mem::size_of::<D>()
407 .max(std::mem::size_of::<A>())
408 .max(std::mem::size_of::<B>()),
409 &|offsets, len, strides| {
410 unsafe {
412 inner_loop_update3::<D, A, B, OpD, OpA, OpB>(
413 dp.get().offset(offsets[0]),
414 strides[0],
415 ap.get().offset(offsets[1]).cast_const(),
416 strides[1],
417 bp.get().offset(offsets[2]).cast_const(),
418 strides[2],
419 len,
420 &f,
421 );
422 }
423 },
424 )
425}
426
427#[cfg(test)]
428#[path = "update_view/tests/tests.rs"]
429mod tests;