Skip to main content

rten_simd/arch/
generic.rs

1use std::array;
2use std::mem::transmute;
3
4use crate::f16;
5use crate::ops::{
6    BitOps, Concat, Extend, FloatOps, IntOps, Interleave, MaskOps, NarrowSaturate, NumOps,
7    SignedIntOps, ToFloat,
8};
9use crate::{Isa, Mask, Simd};
10
11// Size of SIMD vector in 32-bit lanes.
12const LEN_X32: usize = 4;
13
14macro_rules! simd_type {
15    ($simd:ident, $elem:ty, $len:expr) => {
16        #[repr(align(16))]
17        #[derive(Copy, Clone, Debug)]
18        pub struct $simd([$elem; $len]);
19
20        impl $simd {
21            /// Apply a unary operation to each lane of this vector.
22            #[allow(unused)]
23            #[inline]
24            fn map<U, F: Fn($elem) -> U, R>(self, op: F) -> R
25            where
26                R: From<[U; $len]>,
27            {
28                self.0.map(op).into()
29            }
30
31            /// Apply a binary operation to pairs of elements from `self` and `y`.
32            #[allow(unused)]
33            #[inline]
34            fn map_with<U, F: Fn($elem, $elem) -> U, R>(self, y: Self, op: F) -> R
35            where
36                R: From<[U; $len]>,
37            {
38                array::from_fn(|i| op(self.0[i], y.0[i])).into()
39            }
40        }
41
42        impl From<[$elem; $len]> for $simd {
43            fn from(val: [$elem; $len]) -> $simd {
44                $simd(val)
45            }
46        }
47    };
48}
49
50// Define SIMD vector types.
51simd_type!(F32x4, f32, LEN_X32);
52simd_type!(I32x4, i32, LEN_X32);
53simd_type!(I16x8, i16, LEN_X32 * 2);
54simd_type!(I8x16, i8, LEN_X32 * 4);
55simd_type!(U8x16, u8, LEN_X32 * 4);
56simd_type!(U16x8, u16, LEN_X32 * 2);
57simd_type!(U32x4, u32, LEN_X32);
58simd_type!(F16x8, f16, LEN_X32 * 2);
59
60// Define mask vector types. `Mn` is a mask for a vector with n-bit lanes.
61simd_type!(M32, i32, LEN_X32);
62simd_type!(M16, i16, LEN_X32 * 2);
63simd_type!(M8, i8, LEN_X32 * 4);
64
65#[derive(Copy, Clone)]
66pub struct GenericIsa {
67    _private: (),
68}
69
70impl GenericIsa {
71    pub fn new() -> Self {
72        GenericIsa { _private: () }
73    }
74}
75
76impl Default for GenericIsa {
77    fn default() -> Self {
78        Self::new()
79    }
80}
81
82// Safety: Instructions used by generic ISA are always supported.
83unsafe impl Isa for GenericIsa {
84    type M32 = M32;
85    type M16 = M16;
86    type M8 = M8;
87    type F32 = F32x4;
88    type I32 = I32x4;
89    type I16 = I16x8;
90    type I8 = I8x16;
91    type U8 = U8x16;
92    type U16 = U16x8;
93    type U32 = U32x4;
94    type F16 = F16x8;
95    type Bits = I32x4;
96
97    fn f32(
98        self,
99    ) -> impl FloatOps<f32, Simd = Self::F32, Int = Self::I32>
100    + NarrowSaturate<f32, f16, Output = Self::F16> {
101        self
102    }
103
104    fn f16(self) -> impl Extend<f16, Output = Self::F32, Simd = Self::F16> {
105        self
106    }
107
108    fn i32(
109        self,
110    ) -> impl SignedIntOps<i32, Simd = Self::I32>
111    + NarrowSaturate<i32, i16, Output = Self::I16>
112    + Concat<i32>
113    + ToFloat<i32, Output = Self::F32> {
114        self
115    }
116
117    fn i16(
118        self,
119    ) -> impl SignedIntOps<i16, Simd = Self::I16>
120    + NarrowSaturate<i16, u8, Output = Self::U8>
121    + Extend<i16, Output = Self::I32>
122    + Interleave<i16> {
123        self
124    }
125
126    fn i8(
127        self,
128    ) -> impl SignedIntOps<i8, Simd = Self::I8> + Extend<i8, Output = Self::I16> + Interleave<i8>
129    {
130        self
131    }
132
133    fn u8(
134        self,
135    ) -> impl IntOps<u8, Simd = Self::U8> + Extend<u8, Output = Self::U16> + Interleave<u8> {
136        self
137    }
138
139    fn u16(self) -> impl IntOps<u16, Simd = Self::U16> {
140        self
141    }
142
143    fn m32(self) -> impl MaskOps<Self::M32> {
144        self
145    }
146
147    fn m16(self) -> impl MaskOps<Self::M16> {
148        self
149    }
150
151    fn m8(self) -> impl MaskOps<Self::M8> {
152        self
153    }
154}
155
156macro_rules! bit_ops_common {
157    ($simd:ident, $elem:ty, $len:expr, $mask:ident) => {
158        #[inline]
159        fn len(self) -> usize {
160            $len
161        }
162
163        #[inline]
164        fn first_n_mask(self, n: usize) -> $mask {
165            let mask = std::array::from_fn(|i| if i < n { !0 } else { 0 });
166            $mask(mask)
167        }
168
169        #[inline]
170        unsafe fn load_ptr_mask(
171            self,
172            ptr: *const <$simd as Simd>::Elem,
173            mask: <$simd as Simd>::Mask,
174        ) -> $simd {
175            let mask_array = mask.0;
176            let mut vec = <Self as BitOps<$elem>>::zero(self).0;
177            for i in 0..mask_array.len() {
178                if mask_array[i] != 0 {
179                    vec[i] = *ptr.add(i);
180                }
181            }
182            self.load_ptr(vec.as_ref().as_ptr())
183        }
184
185        #[inline]
186        unsafe fn store_ptr_mask(
187            self,
188            x: $simd,
189            ptr: *mut <$simd as Simd>::Elem,
190            mask: <$simd as Simd>::Mask,
191        ) {
192            let mask_array = mask.0;
193            let x_array = x.0;
194            for i in 0..<Self as BitOps<$elem>>::len(self) {
195                if mask_array[i] != 0 {
196                    *ptr.add(i) = x_array[i];
197                }
198            }
199        }
200
201        #[inline]
202        fn splat(self, x: $elem) -> $simd {
203            $simd([x; $len])
204        }
205
206        #[inline]
207        unsafe fn load_ptr(self, ptr: *const $elem) -> $simd {
208            let xs = array::from_fn(|i| *ptr.add(i));
209            $simd(xs)
210        }
211
212        #[inline]
213        fn select(self, x: $simd, y: $simd, mask: <$simd as Simd>::Mask) -> $simd {
214            let xs = array::from_fn(|i| if mask.0[i] != 0 { x.0[i] } else { y.0[i] });
215            $simd(xs)
216        }
217
218        #[inline]
219        unsafe fn store_ptr(self, x: $simd, ptr: *mut $elem) {
220            for i in 0..$len {
221                *ptr.add(i) = x.0[i];
222            }
223        }
224    };
225}
226
227macro_rules! num_ops_common {
228    ($simd:ident, $mask:ident) => {
229        #[inline]
230        fn add(self, x: $simd, y: $simd) -> $simd {
231            x.map_with(y, |x, y| x + y)
232        }
233
234        #[inline]
235        fn sub(self, x: $simd, y: $simd) -> $simd {
236            x.map_with(y, |x, y| x - y)
237        }
238
239        #[inline]
240        fn mul(self, x: $simd, y: $simd) -> $simd {
241            x.map_with(y, |x, y| x * y)
242        }
243
244        #[inline]
245        fn mul_add(self, a: $simd, b: $simd, c: $simd) -> $simd {
246            let xs = array::from_fn(|i| a.0[i] * b.0[i] + c.0[i]);
247            $simd(xs)
248        }
249
250        #[inline]
251        fn eq(self, x: $simd, y: $simd) -> $mask {
252            x.map_with(y, |x, y| if x == y { !0 } else { 0 })
253        }
254
255        #[inline]
256        fn ge(self, x: $simd, y: $simd) -> $mask {
257            x.map_with(y, |x, y| if x >= y { !0 } else { 0 })
258        }
259
260        #[inline]
261        fn gt(self, x: $simd, y: $simd) -> $mask {
262            x.map_with(y, |x, y| if x > y { !0 } else { 0 })
263        }
264
265        #[inline]
266        fn min(self, x: $simd, y: $simd) -> $simd {
267            x.map_with(y, |x, y| x.min(y))
268        }
269
270        #[inline]
271        fn max(self, x: $simd, y: $simd) -> $simd {
272            x.map_with(y, |x, y| x.max(y))
273        }
274    };
275}
276
277macro_rules! simd_int_ops_common {
278    ($simd:ty) => {
279        #[inline]
280        fn and(self, x: $simd, y: $simd) -> $simd {
281            x.map_with(y, |x, y| x & y)
282        }
283
284        #[inline]
285        fn or(self, x: $simd, y: $simd) -> $simd {
286            x.map_with(y, |x, y| x | y)
287        }
288
289        #[inline]
290        fn not(self, x: $simd) -> $simd {
291            x.map(|x| !x)
292        }
293
294        #[inline]
295        fn xor(self, x: $simd, y: $simd) -> $simd {
296            x.map_with(y, |x, y| x ^ y)
297        }
298    };
299}
300
301unsafe impl BitOps<f32> for GenericIsa {
302    type Simd = F32x4;
303
304    bit_ops_common!(F32x4, f32, 4, M32);
305
306    #[inline]
307    fn and(self, x: F32x4, y: F32x4) -> F32x4 {
308        x.map_with(y, |x, y| f32::from_bits(x.to_bits() & y.to_bits()))
309    }
310
311    #[inline]
312    fn not(self, x: F32x4) -> F32x4 {
313        x.map(|x| f32::from_bits(!x.to_bits()))
314    }
315
316    #[inline]
317    fn or(self, x: F32x4, y: F32x4) -> F32x4 {
318        x.map_with(y, |x, y| f32::from_bits(x.to_bits() | y.to_bits()))
319    }
320
321    #[inline]
322    fn xor(self, x: F32x4, y: F32x4) -> F32x4 {
323        x.map_with(y, |x, y| f32::from_bits(x.to_bits() ^ y.to_bits()))
324    }
325}
326
327unsafe impl NumOps<f32> for GenericIsa {
328    num_ops_common!(F32x4, M32);
329}
330
331impl FloatOps<f32> for GenericIsa {
332    type Int = <Self as Isa>::I32;
333
334    #[inline]
335    fn div(self, x: F32x4, y: F32x4) -> F32x4 {
336        x.map_with(y, |x, y| x / y)
337    }
338
339    #[inline]
340    fn round_ties_even(self, x: F32x4) -> F32x4 {
341        x.map(|x| x.round_ties_even())
342    }
343
344    #[inline]
345    fn neg(self, x: F32x4) -> F32x4 {
346        x.map(|x| -x)
347    }
348
349    #[inline]
350    fn abs(self, x: F32x4) -> F32x4 {
351        x.map(|x| x.abs())
352    }
353
354    #[inline]
355    fn to_int_trunc(self, x: F32x4) -> Self::Int {
356        x.map(|x| x as i32)
357    }
358
359    #[inline]
360    fn to_int_round(self, x: F32x4) -> Self::Int {
361        x.map(|x| x.round_ties_even() as i32)
362    }
363}
364
365macro_rules! impl_simd_int_ops {
366    ($simd:ident, $elem:ty, $len:expr, $mask:ident) => {
367        unsafe impl BitOps<$elem> for GenericIsa {
368            type Simd = $simd;
369
370            bit_ops_common!($simd, $elem, $len, $mask);
371            simd_int_ops_common!($simd);
372        }
373
374        unsafe impl NumOps<$elem> for GenericIsa {
375            num_ops_common!($simd, $mask);
376        }
377
378        impl IntOps<$elem> for GenericIsa {
379            #[inline]
380            fn shift_left<const SHIFT: i32>(self, x: $simd) -> $simd {
381                x.map(|x| x << SHIFT)
382            }
383
384            #[inline]
385            fn shift_right<const SHIFT: i32>(self, x: $simd) -> $simd {
386                x.map(|x| x >> SHIFT)
387            }
388        }
389    };
390}
391
392macro_rules! impl_simd_signed_int_ops {
393    ($simd:ident, $elem:ty, $len:expr, $mask:ident) => {
394        impl_simd_int_ops!($simd, $elem, $len, $mask);
395
396        impl SignedIntOps<$elem> for GenericIsa {
397            #[inline]
398            fn neg(self, x: $simd) -> $simd {
399                x.map(|x| -x)
400            }
401        }
402    };
403}
404
405impl_simd_signed_int_ops!(I32x4, i32, 4, M32);
406impl_simd_signed_int_ops!(I16x8, i16, 8, M16);
407impl_simd_signed_int_ops!(I8x16, i8, 16, M8);
408
409macro_rules! impl_extend {
410    ($src:ty, $elem:ty, $dst:ty) => {
411        impl Extend<$elem> for GenericIsa {
412            type Output = $dst;
413
414            fn extend_low(self, x: $src) -> $dst {
415                let extended = x.0.map(|x| x as <$dst as Simd>::Elem);
416                let low = array::from_fn(|i| extended[i]);
417                low.into()
418            }
419
420            fn extend_high(self, x: $src) -> $dst {
421                let extended = x.0.map(|x| x as <$dst as Simd>::Elem);
422                let high = array::from_fn(|i| extended[i + extended.len() / 2]);
423                high.into()
424            }
425        }
426    };
427}
428impl_extend!(I8x16, i8, I16x8);
429impl_extend!(I16x8, i16, I32x4);
430impl_extend!(U8x16, u8, U16x8);
431
432macro_rules! impl_concat {
433    ($elem:ty, $simd:ty) => {
434        impl Concat<$elem> for GenericIsa {
435            fn concat_low(self, a: $simd, b: $simd) -> $simd {
436                let half_len = a.0.len() / 2;
437                array::from_fn(|i| {
438                    if i < half_len {
439                        a.0[i]
440                    } else {
441                        b.0[i - half_len]
442                    }
443                })
444                .into()
445            }
446
447            fn concat_high(self, a: $simd, b: $simd) -> $simd {
448                let half_len = a.0.len() / 2;
449                array::from_fn(|i| {
450                    if i < half_len {
451                        a.0[half_len + i]
452                    } else {
453                        b.0[i]
454                    }
455                })
456                .into()
457            }
458        }
459    };
460}
461
462impl_concat!(i32, I32x4);
463
464macro_rules! impl_interleave {
465    ($elem:ty, $simd:ty) => {
466        impl Interleave<$elem> for GenericIsa {
467            fn interleave_low(self, a: $simd, b: $simd) -> $simd {
468                array::from_fn(|i| if i % 2 == 0 { a.0[i / 2] } else { b.0[i / 2] }).into()
469            }
470
471            fn interleave_high(self, a: $simd, b: $simd) -> $simd {
472                let start = a.0.len() / 2;
473                array::from_fn(|i| {
474                    if i % 2 == 0 {
475                        a.0[start + i / 2]
476                    } else {
477                        b.0[start + i / 2]
478                    }
479                })
480                .into()
481            }
482        }
483    };
484}
485impl_interleave!(i8, I8x16);
486impl_interleave!(i16, I16x8);
487impl_interleave!(u8, U8x16);
488
489impl_simd_int_ops!(U8x16, u8, 16, M8);
490impl_simd_int_ops!(U16x8, u16, 8, M16);
491
492impl ToFloat<i32> for GenericIsa {
493    type Output = F32x4;
494
495    fn to_float(self, x: I32x4) -> Self::Output {
496        F32x4(x.0.map(|x| x as f32))
497    }
498}
499
500trait NarrowSaturateElem<T> {
501    fn narrow_saturate(self) -> T;
502}
503
504impl NarrowSaturateElem<i16> for i32 {
505    fn narrow_saturate(self) -> i16 {
506        self.clamp(i16::MIN as i32, i16::MAX as i32) as i16
507    }
508}
509
510impl NarrowSaturateElem<u8> for i16 {
511    fn narrow_saturate(self) -> u8 {
512        self.clamp(u8::MIN as i16, u8::MAX as i16) as u8
513    }
514}
515
516macro_rules! impl_narrow {
517    ($from:ident, $from_elem:ty, $to:ident, $to_elem:ty) => {
518        impl NarrowSaturate<$from_elem, $to_elem> for GenericIsa {
519            type Output = $to;
520
521            fn narrow_saturate(self, lo: $from, hi: $from) -> $to {
522                let mid = lo.0.len() / 2;
523                let xs = array::from_fn(|i| {
524                    let x = if i < mid { lo.0[i] } else { hi.0[i] };
525                    x.narrow_saturate()
526                });
527                $to(xs)
528            }
529        }
530    };
531}
532impl_narrow!(I32x4, i32, I16x8, i16);
533impl_narrow!(I16x8, i16, U8x16, u8);
534
535unsafe impl BitOps<f16> for GenericIsa {
536    type Simd = F16x8;
537
538    bit_ops_common!(F16x8, f16, 8, M16);
539
540    #[inline]
541    fn and(self, x: F16x8, y: F16x8) -> F16x8 {
542        x.map_with(y, |x, y| f16::from_bits(x.to_bits() & y.to_bits()))
543    }
544
545    #[inline]
546    fn not(self, x: F16x8) -> F16x8 {
547        x.map(|x| f16::from_bits(!x.to_bits()))
548    }
549
550    #[inline]
551    fn or(self, x: F16x8, y: F16x8) -> F16x8 {
552        x.map_with(y, |x, y| f16::from_bits(x.to_bits() | y.to_bits()))
553    }
554
555    #[inline]
556    fn xor(self, x: F16x8, y: F16x8) -> F16x8 {
557        x.map_with(y, |x, y| f16::from_bits(x.to_bits() ^ y.to_bits()))
558    }
559}
560
561impl Extend<f16> for GenericIsa {
562    type Output = F32x4;
563
564    fn extend_low(self, x: F16x8) -> F32x4 {
565        let vals = x.0.map(|v| v.to_f32());
566        let low = array::from_fn(|i| vals[i]);
567        low.into()
568    }
569
570    fn extend_high(self, x: F16x8) -> F32x4 {
571        let vals = x.0.map(|v| v.to_f32());
572        let mid = vals.len() / 2;
573        let high = array::from_fn(|i| vals[i + mid]);
574        high.into()
575    }
576}
577
578impl NarrowSaturate<f32, f16> for GenericIsa {
579    type Output = F16x8;
580
581    fn narrow_saturate(self, low: F32x4, high: F32x4) -> F16x8 {
582        let mid = low.0.len();
583        let xs = array::from_fn(|i| {
584            let v = if i < mid { low.0[i] } else { high.0[i - mid] };
585            f16::from_f32(v)
586        });
587        F16x8(xs)
588    }
589}
590
591macro_rules! impl_mask {
592    ($mask:ident, $len:expr) => {
593        impl Mask for $mask {
594            type Array = [bool; $len];
595
596            #[inline]
597            fn to_array(self) -> Self::Array {
598                let array = self.0;
599                array::from_fn(|i| array[i] != 0)
600            }
601        }
602
603        unsafe impl MaskOps<$mask> for GenericIsa {
604            #[inline]
605            fn and(self, x: $mask, y: $mask) -> $mask {
606                let xs = array::from_fn(|i| x.0[i] & y.0[i]);
607                $mask(xs)
608            }
609
610            #[inline]
611            fn any(self, x: $mask) -> bool {
612                x.0.iter().any(|x| *x != 0)
613            }
614
615            #[inline]
616            fn all(self, x: $mask) -> bool {
617                x.0.iter().all(|x| *x != 0)
618            }
619        }
620    };
621}
622
623impl_mask!(M32, LEN_X32);
624impl_mask!(M16, LEN_X32 * 2);
625impl_mask!(M8, LEN_X32 * 4);
626
627macro_rules! impl_simd {
628    ($simd:ty, $elem:ty, $mask:ty, $len:expr) => {
629        impl Simd for $simd {
630            type Mask = $mask;
631            type Elem = $elem;
632            type Array = [$elem; $len];
633            type Isa = GenericIsa;
634
635            #[inline]
636            fn to_bits(self) -> <Self::Isa as Isa>::Bits {
637                #[allow(clippy::useless_transmute)]
638                I32x4(unsafe { transmute::<[$elem; $len], [i32; LEN_X32]>(self.0) })
639            }
640
641            #[inline]
642            fn from_bits(bits: <Self::Isa as Isa>::Bits) -> Self {
643                #[allow(clippy::useless_transmute)]
644                Self(unsafe { transmute::<[i32; LEN_X32], [$elem; $len]>(bits.0) })
645            }
646
647            #[inline]
648            fn to_array(self) -> Self::Array {
649                self.0
650            }
651        }
652    };
653}
654
655impl_simd!(F32x4, f32, M32, 4);
656impl_simd!(F16x8, f16, M16, 8);
657impl_simd!(I32x4, i32, M32, 4);
658impl_simd!(I16x8, i16, M16, 8);
659impl_simd!(I8x16, i8, M8, 16);
660impl_simd!(U8x16, u8, M8, 16);
661impl_simd!(U16x8, u16, M16, 8);
662impl_simd!(U32x4, u32, M32, 4);