11use super :: * ;
22
3+ use crate :: fallthrough;
4+ use crate :: underlying:: const_as;
5+
6+ /// Aux function: adapter for `Int::multiword_shl<64>`.
7+ ///
8+ /// Compute the result of a multiword left-shift. The return value is `(hi, lo, shift_words)`, such
9+ /// that, in terms of infinite precision arithmetic:
10+ ///
11+ /// ```ignore
12+ /// self << n = (hi << Self::BITS + lo) << (shift_words * 64)
13+ /// ```
14+ fn multiword_shl_64 < Int : crate :: Int > ( x : Int , n : u32 ) -> ( u64 , u64 , usize ) {
15+ use crate :: underlying:: Sealed ;
16+ let x = const_as :: < Int , i64 > ( x) ;
17+ let ( hi, lo, offset_bits) = x. multiword_shl :: < 64 > ( n) ;
18+ let offset_bytes = offset_bits as usize / 64 ;
19+ ( hi as u64 , lo as u64 , offset_bytes)
20+ }
21+
322impl <
423 const N : u32 ,
524 const ES : u32 ,
625 const SIZE : usize ,
726> Quire < N , ES , SIZE > {
827 /// The core algorithm of the quire: adding a fixed-point number, represented as an array of
9- /// `limbs` in big -endian order, shifted `offset` bytes from the right, and sign-extended to the
10- /// size of the quire, to `self`.
28+ /// `limbs` in little -endian order, shifted `offset` bytes from the right, and sign-extended to
29+ /// the size of the quire, to `self`.
1130 ///
1231 /// In other words, the "logical" number to add to the quire is `limbs`, padded with `offset`
1332 /// bits on the right equal to 0, and infinite bits on the left equal to the msb of bytes.
@@ -22,58 +41,77 @@ impl<
2241 ///
2342 /// # Safety
2443 ///
25- /// `limbs.len() * size_of::<Int>() + offset < SIZE` must hold, or calling this function
44+ /// `limbs.len() * size_of::<Int>() + offset ≤ SIZE` must hold, or calling this function
2645 /// is *undefined behaviour*.
27- pub ( crate ) unsafe fn accumulate < const L : usize , Int : crate :: Int > (
46+ ///
47+ /// # Visual example:
48+ ///
49+ /// ```text
50+ /// self = [self[0], self[1], self[2], self[3], self[4], self[6], self[6], self[7]]
51+ /// limbs = [limbs[0], limbs[1]]
52+ /// offset = 3
53+ /// ```
54+ ///
55+ /// then the result is the result of adding the little-endian bignums
56+ ///
57+ /// ```text
58+ /// [self[0], self[1], self[2], self[3] , self[4] , self[5] , self[6] , self[7] ]
59+ /// [0 , 0 , 0 , limbs[0], limbs[1], implicit, implicit, implicit]
60+ /// ```
61+ ///
62+ /// where `implicit = if limbs[1] ≥ 0 {0} else {-1}`.
63+ /*#[inline(always)]*/ // TODO Unclear: benefits round_from(posit) by 30% but worsens += posit by 70%
64+ pub ( crate ) unsafe fn accumulate < const L : usize > (
2865 & mut self ,
29- limbs : [ Int ; L ] ,
66+ limbs : & [ u64 ; L ] ,
3067 offset : usize ,
3168 ) {
32- // The `accumulate` function has three parts:
33- //
34- // - We need to add (with carry) the `limbs` to the quire.
35- // - Then we need to propagate any carry that might exist, up until the end of the quire.
36- // - Then we need to check if we overflow the quire sum limit (highly unlikely), in which
37- // case the result should be NaR.
38- debug_assert ! ( core:: mem:: size_of_val( & limbs) + offset < SIZE ) ;
69+ let quire: & mut [ u64 ] = self . as_u64_array_mut ( ) ;
70+ let len_u64 = quire. len ( ) ;
3971
40- // Add the `limbs` with carry.
41- //
42- // SAFETY: As a precondition, `offset < SIZE`, so `ptr` is in bounds of the array. But more
43- // strongly, `offset + L * size_of::<Int>() < SIZE`, so `ptr` will _remain_ in bounds
44- // throughout this loop. Finally, the `offset` is guaranteed to be aligned to `Int`, and so is
45- // the quire itself (which is aligned to 128 bits). Therefore: the `src` and `dst` pointers
46- // are always in bounds and aligned to `Int`.
47- //
48- // The following snippet seems to have great codegen: the fixed-size loop of `carrying_add`s is
49- // actually compiled to a sequence of `adc` instructions on x86!
50- let base_dst = unsafe { self . 0 . as_mut_ptr_range ( ) . end . byte_sub ( offset) } as * mut Int ;
51- let base_src = limbs. as_ptr_range ( ) . end as * mut Int ;
52- let mut carry = false ;
53- for i in 0 ..L {
54- let dst = unsafe { base_dst. sub ( 1 + i) } ;
55- let src = unsafe { base_src. sub ( 1 + i) } ;
56- let ( result, overflow) = unsafe { Int :: carrying_add ( ( * dst) . from_be ( ) , ( * src) , carry) } ;
57- unsafe { * dst = result. to_be ( ) } ;
58- carry = overflow;
72+ if cfg ! ( debug_assertions) {
73+ debug_assert ! ( offset + limbs. len( ) <= quire. len( ) )
74+ } else {
75+ // SAFETY: Precondition.
76+ unsafe { core:: hint:: assert_unchecked ( offset + limbs. len ( ) <= quire. len ( ) ) }
5977 }
6078
61- // We're done with the limbs. Now the only value that needs to be "added" is an eventual carry
62- // and/or sign bit, i.e. a value of +1, 0, or -1. This needs to be propagated until the end.
63- let upper_padding = limbs[ 0 ] >> ( Int :: BITS - 1 ) ;
64- let mut dst = unsafe { base_dst. sub ( 1 + L ) } ;
65- while dst as * const u8 >= self . 0 . as_ptr ( ) {
66- /*if !carry { return }*/
67- let ( result, overflow) = unsafe { ( * dst) . from_be ( ) . carrying_add ( upper_padding, carry) } ;
68- unsafe { * dst = result. to_be ( ) }
69- carry = overflow;
70- dst = unsafe { dst. sub ( 1 ) }
79+ // Part 1: Add `limbs[..]` to `quire[offset .. offset + L]`.
80+ let mut carry = false ;
81+ for i in 0 .. limbs. len ( ) {
82+ // SAFETY: This follows from the above precondition, but we re-assert it to help the
83+ // compiler.
84+ unsafe { core:: hint:: assert_unchecked ( offset + i < quire. len ( ) ) }
85+ let ( r, o) = u64:: carrying_add ( quire[ offset + i] , limbs[ i] , carry) ;
86+ quire[ offset + i] = r;
87+ carry = o;
7188 }
7289
73- /*// If we reach this, then would still be a carry beyond the limits of the quire. In this
74- // (very unlikely) case, we set the quire to NaR.
75- if carry == Int::ZERO { return }
76- *self = Self::NAR*/
90+ // In principle the compiler is able to optimise this, but just in case...
91+ if const { L == Self :: SIZE / 8 } { return }
92+
93+ // Part 2: Add `implicit` to `quire[offset + L ..]`.
94+ let implicit = ( limbs[ L -1 ] as i64 >> 63 ) as u64 ;
95+
96+ fallthrough ! ( offset + limbs. len( ) ,
97+ 0 => if 0 < len_u64 { let ( r, o) = u64 :: carrying_add( quire[ 0 ] , implicit, carry) ; quire[ 0 ] = r; carry = o; } ,
98+ ' l1: 1 => if 1 < len_u64 { let ( r, o) = u64 :: carrying_add( quire[ 1 ] , implicit, carry) ; quire[ 1 ] = r; carry = o; } ,
99+ ' l2: 2 => if 2 < len_u64 { let ( r, o) = u64 :: carrying_add( quire[ 2 ] , implicit, carry) ; quire[ 2 ] = r; carry = o; } ,
100+ ' l3: 3 => if 3 < len_u64 { let ( r, o) = u64 :: carrying_add( quire[ 3 ] , implicit, carry) ; quire[ 3 ] = r; carry = o; } ,
101+ ' l4: 4 => if 4 < len_u64 { let ( r, o) = u64 :: carrying_add( quire[ 4 ] , implicit, carry) ; quire[ 4 ] = r; carry = o; } ,
102+ ' l5: 5 => if 5 < len_u64 { let ( r, o) = u64 :: carrying_add( quire[ 5 ] , implicit, carry) ; quire[ 5 ] = r; carry = o; } ,
103+ ' l6: 6 => if 6 < len_u64 { let ( r, o) = u64 :: carrying_add( quire[ 6 ] , implicit, carry) ; quire[ 6 ] = r; carry = o; } ,
104+ ' l7: 7 => if 7 < len_u64 { let ( r, o) = u64 :: carrying_add( quire[ 7 ] , implicit, carry) ; quire[ 7 ] = r; carry = o; } ,
105+ ' l8: 8 => if 8 < len_u64 { let ( r, o) = u64 :: carrying_add( quire[ 8 ] , implicit, carry) ; quire[ 8 ] = r; carry = o; } ,
106+ ' l9: 9 => if 9 < len_u64 { let ( r, o) = u64 :: carrying_add( quire[ 9 ] , implicit, carry) ; quire[ 9 ] = r; carry = o; } ,
107+ ' l10: 10 => if 10 < len_u64 { let ( r, o) = u64 :: carrying_add( quire[ 10 ] , implicit, carry) ; quire[ 10 ] = r; carry = o; } ,
108+ ' l11: 11 => if 11 < len_u64 { let ( r, o) = u64 :: carrying_add( quire[ 11 ] , implicit, carry) ; quire[ 11 ] = r; carry = o; } ,
109+ ' l12: 12 => if 12 < len_u64 { let ( r, o) = u64 :: carrying_add( quire[ 12 ] , implicit, carry) ; quire[ 12 ] = r; carry = o; } ,
110+ ' l13: 13 => if 13 < len_u64 { let ( r, o) = u64 :: carrying_add( quire[ 13 ] , implicit, carry) ; quire[ 13 ] = r; carry = o; } ,
111+ ' l14: 14 => if 14 < len_u64 { let ( r, o) = u64 :: carrying_add( quire[ 14 ] , implicit, carry) ; quire[ 14 ] = r; carry = o; } ,
112+ ' l15: 15 => if 15 < len_u64 { let ( r, o) = u64 :: carrying_add( quire[ 15 ] , implicit, carry) ; quire[ 15 ] = r; carry = o; } ,
113+ ' z: _ => return ,
114+ ) ;
77115
78116 // TODO: overflows of the quire sum limit should go to NaR!
79117 }
85123 /// `x` must be the result of a [`Posit::decode_regular`] call, or calling this function
86124 /// is *undefined behaviour*.
87125 pub ( crate ) unsafe fn accumulate_decoded < Int : crate :: Int > ( & mut self , x : Decoded < N , ES , Int > ) {
126+ const { assert ! ( Int :: BITS <= 64 , "Quire operations are currently not supported for N > 64" ) } ;
88127 debug_assert ! ( x. exp. abs( ) <= Posit :: <N , ES , Int >:: MAX_EXP + Int :: ONE ) ;
89128
90129 // The quire is a fixed-point accumulator wide enough to accomodate the product of any two
@@ -108,18 +147,41 @@ impl<
108147 // `if const` (well, not quite because of limitations in Rust, but yes, `base_shift` is a
109148 // constant and will be folded out). We simply check if `base_shift - MAX_EXP` can ever be
110149 // negative (a right-shift). If not, this branch is not even included in the binary.
111- if base_shift <= Posit :: < N , ES , Int > :: MAX_EXP && shift < Int :: ZERO {
150+ if base_shift <= Posit :: < N , ES , Int > :: MAX_EXP
151+ && shift < Int :: ZERO {
112152 // Note: no bits of `frac` are actually lost in the right-shift; this is just because `Int`
113153 // itself is wide enough relative to `N` and `ES`.
114154 let shift_right = ( -shift) . as_u32 ( ) ;
115155 debug_assert_eq ! ( x. frac. mask_lsb( shift_right) , Int :: ZERO ) ;
116156 // SAFETY: `limbs` is only one `Int`, and `offset` is `0`.
117- unsafe { self . accumulate ( [ x. frac >> shift_right] , 0 ) }
118- } else {
157+ let lo = const_as :: < Int , i64 > ( x. frac ) >> shift_right;
158+ unsafe { self . accumulate ( & [ lo as u64 ] , 0 ) }
159+ }
160+
161+ // Normal case: we multiword-shift left by `shift_left` bits.
162+ else {
119163 let shift_left = shift. as_u32 ( ) ;
120- let ( hi, lo, offset) = x. frac . multiword_shl ( shift_left) ;
121- // SAFETY: `x.exp` is between `MIN_EXP` and `MAX_EXP`, so the `offset` is always `< SIZE / 2`.
122- unsafe { self . accumulate ( [ hi, lo] , offset) }
164+ let ( hi, lo, offset) = multiword_shl_64 ( x. frac , shift_left) ;
165+
166+ // Another edge case: we check in `Self::MIN_SIZE` that we have enough bits. But if we have
167+ // less than 64 to spare, then the `hi` word might be pushed out of the quire. In this case,
168+ // we must be careful to **not** add `&[lo, hi]`, but just `&[lo]`, or we would write out of
169+ // bounds.
170+ //
171+ // Note that again, like the previous edge case above, this only happens on a small portion
172+ // of types (e.g. among the standard types only on p8 and q8), and for types where it cannot
173+ // happen the branch is not even included in the binary.
174+ if Self :: WIDTH + 2 + Posit :: < N , ES , Int > :: MAX_EXP . as_u32 ( ) > Self :: BITS - 64
175+ && offset == Self :: SIZE / 8 - 1 {
176+ debug_assert ! ( ( lo as i64 ) >= 0 && hi as i64 == 0 || ( lo as i64 ) < 0 && hi as i64 == -1 ) ;
177+ unsafe { self . accumulate ( & [ lo] , offset) }
178+ }
179+
180+ // Otherwise, business as usual
181+ else {
182+ // SAFETY: `x.exp` is between `MIN_EXP` and `MAX_EXP`, so the `offset` is always `< SIZE / 2`.
183+ unsafe { self . accumulate ( & [ lo, hi] , offset) }
184+ }
123185 }
124186 }
125187}
0 commit comments