1use std::fmt::Debug;
2use std::marker::PhantomData;
3
4use tract_data::TractResult;
5use tract_data::internal::TensorView;
6
7use crate::frame::element_wise_helper::TempBuffer;
8use crate::{BinFn, LADatum};
9
10macro_rules! routine_unicast_rust {
17 (arm; $($rest:tt)*) => { routine_unicast_rust!(@ arm, target_arch = "arm"; $($rest)*); };
18 (aarch64; $($rest:tt)*) => {
19 routine_unicast_rust!(@ aarch64, target_arch = "aarch64"; $($rest)*);
20 };
21 (x86_64; $($rest:tt)*) => {
22 routine_unicast_rust!(@ x86_64, target_arch = "x86_64"; $($rest)*);
23 };
24 (riscv64; $($rest:tt)*) => {
25 routine_unicast_rust!(@ riscv64, target_arch = "riscv64"; $($rest)*);
26 };
27 (wasm32; $($rest:tt)*) => {
28 routine_unicast_rust!(@ wasm32,
29 all(target_arch = "wasm32", target_feature = "simd128"); $($rest)*);
30 };
31 (generic; $($rest:tt)*) => { routine_unicast_rust!(@ generic, all(); $($rest)*); };
32
33 (@ $arch:ident, $built:meta; $ti:ident, $ker:ident, $nr:expr, $alignment_items:expr,
34 $run:item, op($op:ident) $(, isa($($isa:ident),+))?) => {
35 unicast_kernel!(@ $built; $ti, $ker, $nr, $alignment_items, $run);
36 paste! {
37 submit_routine!($arch; [<Bin $ti:upper>], BinUnicast($op), $ker $(, isa($($isa),+))?);
38 #[cfg(test)]
39 mod [<test_ $ker:snake>] {
40 use super::*;
41 unicast_frame_tests!(
42 cfg!($built)
43 && $crate::isa::IsaReq::ANY
44 $(.needing(&[$($crate::isa::Isa::$isa),+]))?
45 .satisfied_by($crate::isa::native()),
46 $ti,
47 $ker,
48 bin_reference!($op)
49 );
50 }
51 }
52 };
53}
54
55macro_rules! unicast_kernel {
56 (arm; $($rest:tt)*) => { unicast_kernel!(@ target_arch = "arm"; $($rest)*); };
57 (aarch64; $($rest:tt)*) => { unicast_kernel!(@ target_arch = "aarch64"; $($rest)*); };
58 (x86_64; $($rest:tt)*) => { unicast_kernel!(@ target_arch = "x86_64"; $($rest)*); };
59 (riscv64; $($rest:tt)*) => { unicast_kernel!(@ target_arch = "riscv64"; $($rest)*); };
60 (wasm32; $($rest:tt)*) => { unicast_kernel!(@ all(target_arch = "wasm32", target_feature = "simd128"); $($rest)*); };
61
62 (@ $built:meta; $ti:ident, $func:ident, $nr:expr, $alignment_items:expr, $run:item) => {
63 #[cfg($built)]
64 unicast_kernel!($ti, $func, $nr, $alignment_items, $run);
65 #[cfg(not($built))]
66 unicast_kernel!($ti, $func, $nr, $alignment_items,
67 fn run(_a: &mut [$ti], _b: &[$ti]) {
68 panic!(concat!(stringify!($func), ": kernel not built for this target"))
69 }
70 );
71 };
72
73 ($ti: ident, $func: ident, $nr: expr, $alignment_items: expr, $run: item) => {
74 paste! {
75 #[derive(Copy, Clone, Debug)]
76 #[allow(non_camel_case_types)]
77 pub struct $func;
78
79 impl crate::frame::unicast::UnicastKer<$ti> for $func {
80 #[inline(always)]
81 fn name() -> &'static str {
82 stringify!($func)
83 }
84 #[inline(always)]
85 fn nr() -> usize {
86 $nr
87 }
88 #[inline(always)]
89 fn alignment_items() -> usize {
90 $alignment_items
91 }
92 $run
93 }
94 }
95 };
96}
97
98pub trait Unicast<T>: Send + Sync + Debug + dyn_clone::DynClone
99where
100 T: Copy + Debug + PartialEq + Send + Sync,
101{
102 fn name(&self) -> &'static str;
103 fn run(&self, a: &mut [T], b: &[T]) -> TractResult<()>;
104}
105
106dyn_clone::clone_trait_object!(<T> Unicast<T> where T: Copy);
107
108#[derive(Debug, Clone, new)]
109pub struct UnicastImpl<K, T>
110where
111 T: LADatum,
112 K: UnicastKer<T> + Clone,
113{
114 phantom: PhantomData<(K, T)>,
115}
116
117impl<K, T> UnicastImpl<K, T>
118where
119 T: LADatum,
120 K: UnicastKer<T> + Clone,
121{
122}
123impl<K, T> Unicast<T> for UnicastImpl<K, T>
124where
125 T: LADatum,
126 K: UnicastKer<T> + Clone,
127{
128 fn name(&self) -> &'static str {
129 K::name()
130 }
131 fn run(&self, a: &mut [T], b: &[T]) -> TractResult<()> {
132 unicast_with_alignment(a, b, |a, b| K::run(a, b), K::nr(), K::alignment_bytes())
133 }
134}
135
136pub trait UnicastKer<T>: Send + Sync + Debug + dyn_clone::DynClone + Clone + 'static
137where
138 T: LADatum,
139{
140 fn name() -> &'static str;
141 fn alignment_bytes() -> usize {
142 Self::alignment_items() * T::datum_type().size_of()
143 }
144 fn alignment_items() -> usize;
145 fn nr() -> usize;
146 fn run(a: &mut [T], b: &[T]);
147 fn bin() -> Box<BinFn> {
148 Box::new(|a: &mut TensorView, b: &TensorView| {
149 let a_slice = a.as_slice_mut()?;
150 let b_slice = b.as_slice()?;
151 UnicastImpl::<Self, T>::new().run(a_slice, b_slice)
152 })
153 }
154}
155
156std::thread_local! {
157 static TMP: std::cell::RefCell<(TempBuffer, TempBuffer)> = std::cell::RefCell::new((TempBuffer::default(), TempBuffer::default()));
158}
159
160pub(crate) fn unicast_with_alignment<T>(
161 a: &mut [T],
162 b: &[T],
163 f: impl Fn(&mut [T], &[T]),
164 nr: usize,
165 alignment_bytes: usize,
166) -> TractResult<()>
167where
168 T: LADatum,
169{
170 if a.is_empty() {
171 return Ok(());
172 }
173 unsafe {
174 TMP.with(|buffers| {
175 let mut buffers = buffers.borrow_mut();
176 buffers.0.ensure(nr * T::datum_type().size_of(), alignment_bytes);
177 buffers.1.ensure(nr * T::datum_type().size_of(), alignment_bytes);
178 let tmp_a = std::slice::from_raw_parts_mut(buffers.0.buffer as *mut T, nr);
179 let tmp_b = std::slice::from_raw_parts_mut(buffers.1.buffer as *mut T, nr);
180 let mut compute_via_temp_buffer = |a: &mut [T], b: &[T]| {
181 tmp_a[..a.len()].copy_from_slice(a);
182 tmp_b[..b.len()].copy_from_slice(b);
183 f(tmp_a, tmp_b);
184 a.copy_from_slice(&tmp_a[..a.len()])
185 };
186
187 let mut num_element_processed = 0;
188 let a_prefix_len = a.as_ptr().align_offset(alignment_bytes).min(a.len());
189 let b_prefix_len = b.as_ptr().align_offset(alignment_bytes).min(b.len());
190 assert!(
191 a_prefix_len == b_prefix_len,
192 "Both inputs should be of the same alignement, got {a_prefix_len:?}, {b_prefix_len:?}"
193 );
194 let mut applied_prefix_len = 0;
195 if a_prefix_len > 0 {
196 let sub_a = &mut a[..a_prefix_len];
198 let sub_b = &b[..a_prefix_len];
199 compute_via_temp_buffer(sub_a, sub_b);
200 num_element_processed += a_prefix_len;
201 applied_prefix_len = a_prefix_len;
202 }
203
204 let num_complete_tiles = (a.len() - applied_prefix_len) / nr;
205 if num_complete_tiles > 0 {
206 let sub_a = &mut a[applied_prefix_len..][..(num_complete_tiles * nr)];
208 let sub_b = &b[applied_prefix_len..][..(num_complete_tiles * nr)];
209 f(sub_a, sub_b);
210 num_element_processed += num_complete_tiles * nr;
211 }
212
213 if num_element_processed < a.len() {
214 compute_via_temp_buffer(
216 &mut a[num_element_processed..],
217 &b[num_element_processed..],
218 );
219 }
220 })
221 }
222 Ok(())
223}
224
225#[cfg(test)]
226#[macro_use]
227pub mod test {
228 use super::*;
229 use crate::LADatum;
230 use proptest::test_runner::{TestCaseError, TestCaseResult};
231 use tract_data::internal::*;
232 use tract_num_traits::{AsPrimitive, Float};
233
234 pub fn test_unicast<K: UnicastKer<T>, T: LADatum>(
235 a: &mut [T],
236 b: &[T],
237 reference: impl Fn(T, T) -> T,
238 ) -> TestCaseResult {
239 crate::setup_test_logger();
240 let op = UnicastImpl::<K, T>::new();
241 let expected = a.iter().zip(b.iter()).map(|(a, b)| (reference)(*a, *b)).collect::<Vec<_>>();
242 op.run(a, b).unwrap();
243 tensor1(a)
244 .close_enough(&tensor1(&expected), true)
245 .map_err(|e| TestCaseError::fail(e.root_cause().to_string()))?;
246 Ok(())
247 }
248
249 pub fn test_unicast_t<K: UnicastKer<T>, T: LADatum + Float>(
250 a: &[f32],
251 b: &[f32],
252 func: impl Fn(T, T) -> T,
253 ) -> TestCaseResult
254 where
255 f32: AsPrimitive<T>,
256 {
257 crate::setup_test_logger();
258 let vec_a: Vec<T> = a.iter().copied().map(|x| x.as_()).collect();
259 let mut a = unsafe { Tensor::from_slice_align(vec_a.as_slice(), vector_size()).unwrap() };
261 let vec_b: Vec<T> = b.iter().copied().map(|x| x.as_()).collect();
262 let b = unsafe { Tensor::from_slice_align(vec_b.as_slice(), vector_size()).unwrap() };
264 crate::frame::unicast::test::test_unicast::<K, _>(
265 a.try_as_plain_mut().unwrap().as_slice_mut::<T>().unwrap(),
266 b.try_as_plain().unwrap().as_slice::<T>().unwrap(),
267 func,
268 )
269 }
270
271 #[macro_export]
272 macro_rules! unicast_frame_tests {
273 ($cond:expr, $t: ty, $ker:ty, $func:expr) => {
274 pastey::paste! {
275 proptest::proptest! {
276 #[test]
277 fn [<prop_ $ker:snake>](
278 (a, b) in proptest::strategy::Strategy::prop_flat_map(
279 0..100_usize,
280 |len| (vec![-25f32..25.0; len], vec![-25f32..25.0; len])
281 )
282 ) {
283 if $cond {
284 $crate::frame::unicast::test::test_unicast_t::<$ker, $t>(&*a, &*b, $func).unwrap()
285 }
286 }
287 }
288
289 #[test]
290 fn [<empty_ $ker:snake>]() {
291 if $cond {
292 $crate::frame::unicast::test::test_unicast_t::<$ker, $t>(&[], &[], $func).unwrap()
293 }
294 }
295 }
296 };
297 }
298}