Line data Source code
1 : #ifndef HEADER_fd_src_ballet_ed25519_avx512_fd_r43x6_h
2 : #define HEADER_fd_src_ballet_ed25519_avx512_fd_r43x6_h
3 :
4 : #if FD_HAS_AVX512
5 :
6 : #include "../../../util/simd/fd_avx.h"
7 : #include "../../../util/simd/fd_avx512.h"
8 :
9 : /* A fd_r43x6_t represents a GF(p) element, where p = 2^255-19, in a
10 : little endian 6 long radix 2^43 limb representation. The 6 limbs are
11 : held in lanes 0 through 5 of an AVX-512 vector. That is, given a
12 : fd_r43x6_t x, the field element represented by x is:
13 :
14 : ( x0 + x1 2^43 + x2 2^86 + x3 2^129 + x4 2^172 + x5 2^215 ) mod p
15 :
16 : where xn is the n-th 64-bit vector lane treated as a long. Lanes 6
17 : and 7 are ignored. The below will often use the shorthand:
18 :
19 : <x0,x1,x2,x3,x4,x5>
20 :
21 : for the above expression.
22 :
23 : This representation is redundant: multiple fd_r43x6_t can represent
24 : the same element. Most functions have restrictions on the which
25 : representations can be used for inputs and which representations they
26 : produce to support high performance implementation and composability.
27 :
28 : Frequently used representations are:
29 :
30 : - arbitrary: limbs are in [-2^63,2^63)
31 : - signed: limbs are in [-2^62,2^62)
32 : - unsigned: limbs are in [0,2^62)
33 : - unreduced: limbs are in [0,2^47)
34 : - unpacked: limbs 0-4 are in [0,2^43), limb 5 is in [0,2^41)
35 : - nearly reduced: limbs 0-4 are in [0,2^43), limb 5 is in [0,2^41), the packed uint256 value is in [0,2*p)
36 : - reduced: limbs 0-4 are in [0,2^43), limb 5 is in [0,2^40), the packed uint256 value is in [0,p)
37 :
38 : As a frequently used shorthand when analyzing range of limbs, unn
39 : indicates limbs are in [0,2^nn) and snn indicates limbs are in
40 : (-2^nn,2^nn).
41 :
42 : Note:
43 :
44 : - There is only one reduced fd_r43x6_t for each element.
45 :
46 : - There are two nearly reduced fd_r43x6_t for each element.
47 :
48 : - reduced is a subset of nearly reduced is a subset of unpacked is a
49 : subset of unreduced is a subset of unsigned is a subset of signed
50 : is a subset arbitrary.
51 :
52 : - unpacked, nearly reduced and reduced fd_r43x6_t be quickly
53 : converted into a packed uint256 value used by various cryptographic
54 : protocols and vice versa.
55 :
56 : - Cheat sheet
57 :
58 : unpack maps uint256 to unpacked
59 : pack maps unpacked to uint256
60 :
61 : * These are used to interface with external protocols.
62 :
63 : fold_unsigned maps unsigned to unreduced
64 : fold_signed maps signed to unreduced
65 :
66 : * fold_* are fast and typically used to keep the ranges of limbs
67 : reasonable in long running calculations without needing to do
68 : more expensive approx_mod_* or mod_* operations.
69 :
70 : approx_mod maps arbitrary to nearly reduced
71 : approx_mod_signed maps signed to nearly reduced
72 : approx_mod_unsigned maps unsigned to nearly reduced
73 : approx_mod_unreduced maps unreduced to nearly reduced
74 : approx_mod_unpacked maps unpacked to nearly reduced
75 :
76 : * approx_mod_* are typically used to get inputs to a long running
77 : calculation into a suitable form or by mod_* below.
78 :
79 : mod maps arbitrary to reduced (equiv to approx_mod / mod_nearly_reduced)
80 : mod_signed maps signed to reduced (equiv to approx_mod_signed / mod_nearly_reduced)
81 : mod_unsigned maps unsigned to reduced (equiv to approx_mod_unsigned / mod_nearly_reduced)
82 : mod_unreduced maps unreduced to reduced (equiv to approx_mod_unreduced / mod_nearly_reduced)
83 : mod_unpacked maps unpacked to reduced (equiv to approx_mod_unpacked / mod_nearly_reduced)
84 : mod_nearly_reduced maps nearly reduced to reduced
85 :
86 : * mod_* are typically used for produce a final unique result at
87 : the end of a long running calculation.
88 :
89 : add_fast maps unreduced x unreduced to unsigned (among others), see fold_unsigned above
90 : sub_fast maps unreduced x unreduced to signed (among others), see fold_signed above
91 : or, reduced x reduced to unsigned (among others), see fold_unsigned above
92 : mul_fast maps unreduced x unreduced to unsigned, see fold_unsigned above
93 : sqr_fast maps unreduced to unsigned, see fold_unsigned above
94 : neg maps unreduced to unreduced
95 : or, reduced to reduced
96 : add maps unreduced x unreduced to unreduced
97 : sub maps unreduced x unreduced to unreduced
98 : mul maps unreduced x unreduced to unreduced
99 : sqr maps unreduced to unreduced
100 : scale maps [0,2^47) x unreduced to unreduced
101 : scaleadd maps unreduced x [0,2^47) x unreduced to unreduced
102 : invert maps unreduced to unreduced
103 : is_nonzero maps signed to [0,1]
104 : diagnose maps signed to [-1,0,1]
105 : pow22523 maps unreduced to unreduced
106 :
107 : * These are used to implement HPC calculations on GF(p) elements. */
108 :
109 242936359 : #define fd_r43x6_t wwl_t
110 :
111 : FD_PROTOTYPES_BEGIN
112 :
113 : /* fd_r43x6(x0,x1,x2,x3,x4,x5) constructs an arbitrary r43x6_t from the
114 : given limbs. Lanes 6 and 7 will be zero. This macro is robust.
115 : Note: implementing via setr was benchmarked as slightly faster than
116 : loading from a stack tmp (probably due to better compiler code gen). */
117 :
118 293473663 : #define fd_r43x6(x0,x1,x2,x3,x4,x5) wwl( (x0),(x1),(x2),(x3),(x4),(x5), 0L,0L )
119 :
120 : /* fd_r43x6_extract_limbs(x,y) extracts the limbs of an arbitrary
121 : fd_r43x6_t x into the longs y0-y5. This is primarily for use in
122 : operations that are not vectorized. This macro is robust. Note:
123 : implementing via extract was benchmarked as slightly faster than
124 : storing to a stack tmp and reloading (probably due to better compiler
125 : code gen). */
126 :
127 295670092 : #define fd_r43x6_extract_limbs(x,y) do { \
128 295670092 : wwl_t _x = (x); \
129 295670092 : __m256i _xl = _mm512_extracti64x4_epi64( _x, 0 ); \
130 295670092 : __m256i _xh = _mm512_extracti64x4_epi64( _x, 1 ); \
131 295670092 : y##0 = _mm256_extract_epi64( _xl, 0 ); \
132 295670092 : y##1 = _mm256_extract_epi64( _xl, 1 ); \
133 295670092 : y##2 = _mm256_extract_epi64( _xl, 2 ); \
134 295670092 : y##3 = _mm256_extract_epi64( _xl, 3 ); \
135 295670092 : y##4 = _mm256_extract_epi64( _xh, 0 ); \
136 295670092 : y##5 = _mm256_extract_epi64( _xh, 1 ); \
137 295670092 : } while(0)
138 :
139 : /* fd_r43x6_zero(), fd_r43x6_one(), fd_r43x6_p(), fd_r43x6_d(),
140 : fd_r43x6_2d(), fd_r43x6_imag() returns the reduced fd_r43x6_t for
141 : zero (reduced), one (reduced), 2^255-19 (the non-trivial nearly
142 : reduced representation), d (reduced), 2*d (reduced) and sqrt(-1)
143 : (reduced) respectively. d is defined as per IETF RFC 8032 Section
144 : 5.1 (page 9) as -121665/121666. imag^2 = -1 mod p = p-1. These
145 : macros are robust. Lanes 6 and 7 will be zero. */
146 :
147 1 : #define fd_r43x6_zero() wwl_zero()
148 300755 : #define fd_r43x6_one() wwl( 1L, 0L, 0L, 0L, 0L, 0L, 0L,0L )
149 20000001 : #define fd_r43x6_p() wwl( 8796093022189L,8796093022207L,8796093022207L,8796093022207L,8796093022207L,1099511627775L, 0L,0L )
150 300754 : #define fd_r43x6_d() wwl( 6365466163363L, 253762649449L, 7518893317L, 260847760460L,7696165686388L, 704489577558L, 0L,0L )
151 : #define fd_r43x6_2d() wwl( 3934839304537L, 507525298899L, 15037786634L, 521695520920L,6596238350568L, 309467527341L, 0L,0L )
152 300754 : #define fd_r43x6_imag() wwl( 3467281080496L,6582290652611L,5210002954932L, 329084955603L,4526638806224L, 373767602335L, 0L,0L )
153 :
154 : /* fd_r43x6_unpack(u) returns an unpacked r43x6_t corresponding to an
155 : arbitrary uint256 stored little endian 4 ulong radix 2^64 limb
156 : representation held in an AVX-2 vector:
157 :
158 : u = u0 + u1 2^64 + u2 2^128 + u3 2^192
159 :
160 : where un is the n-th 64-bit vector lane treated as a ulong. Returned
161 : lanes 6 and 7 will be zero. If u is in [0,2*p), the return will be a
162 : nearly reduced fd_r43x6_t. If u is in [0,p), the return will be a
163 : reduced fd_r43x6_t. */
164 :
165 : FD_FN_CONST static inline fd_r43x6_t
166 32062685 : fd_r43x6_unpack( wv_t u ) {
167 32062685 : wwl_t const zero = wwl_zero();
168 32062685 : wwl_t const perm = wwl( 0x3f3f050403020100L, // r0 = bits 0: 42 (43 bits, zero extend to 64 bits)
169 32062685 : 0x3f3f0a0908070605L, // r1 = bits 43: 85 (43 bits, zero extend to 64 bits)
170 32062685 : 0x3f100f0e0d0c0b0aL, // r2 = bits 86:128 (43 bits, zero extend to 64 bits)
171 32062685 : 0x3f3f151413121110L, // r3 = bits 129:171 (43 bits, zero extend to 64 bits)
172 32062685 : 0x3f3f1a1918171615L, // r4 = bits 172:214 (43 bits, zero extend to 64 bits)
173 32062685 : 0x3f3f1f1e1d1c1b1aL, // r5 = bits 215:255 (41 bits, zero extend to 64 bits)
174 32062685 : 0x3f3f3f3f3f3f3f3fL, // r6 = zero
175 32062685 : 0x3f3f3f3f3f3f3f3fL ); // r7 = zero
176 32062685 : wwl_t const rshift = wwl( 0L, 3L, 6L, 1L, 4L, 7L, 0L, 0L ); // r0/r1/r2/r3/r4/r5 bit 0 is u bit 0/43/86/129/172
177 32062685 : wwl_t const mask = wwl_bcast( (1L<<43)-1L ); // Keep 43 least significant bits for each lane
178 32062685 : return wwl_and( wwl_shru_vector( _mm512_permutexvar_epi8( perm, _mm512_inserti64x4( zero, u, 0 ) ), rshift ), mask );
179 32062685 : }
180 :
181 : /* fd_r43x6_pack(r) is the inverse of fd_r43x6_unpack. r should be an
182 : unpacked fd_r43x6_t. If r is also nearly reduced, the return will be
183 : in [0,2*p). If r is also reduced fd_r43x6_t, the return will be in
184 : [0,p). Ignores lanes 6 and 7. */
185 :
186 : FD_FN_CONST static inline wv_t
187 33210613 : fd_r43x6_pack( fd_r43x6_t r ) {
188 :
189 : /* 43 21
190 : 0 42 43 63
191 : u0 = r0_0 ... r0_42 r1_0 ... r1_20
192 :
193 : 22 42
194 : 0 21 22 63
195 : u1 = r1_21 ... r1_42 r2_0 ... r2_41
196 :
197 : 1 43 20
198 : 0 1 43 44 63
199 : u2 = r2_42 r3_0 ... r3_42 r4_0 ... r4_19
200 :
201 : 23 41
202 : 0 22 23 63
203 : u3 = r4_20 ... r3_42 r5_0 ... r5_40
204 :
205 : t0 t1 t2
206 : u0 = (r0>> 0) | (r1<<43) | (r1<<43); ... Last term redundant to keep vectorized
207 : u1 = (r1>>21) | (r2<<22) | (r2<<22); ... "
208 : u2 = (r2>>42) | (r3<< 1) | (r4<<44);
209 : u3 = (r4>>20) | (r5<<23) | (r5<<23); ... " */
210 :
211 33210613 : wwl_t t0 = wwl_shru_vector( wwl_permute( wwl( 0L, 1L, 2L, 4L, 0L,0L,0L,0L ), r ), wwl( 0L,21L,42L,20L, 0L,0L,0L,0L ) );
212 33210613 : wwl_t t1 = wwl_shl_vector ( wwl_permute( wwl( 1L, 2L, 3L, 5L, 0L,0L,0L,0L ), r ), wwl( 43L,22L, 1L,23L, 0L,0L,0L,0L ) );
213 33210613 : wwl_t t2 = wwl_shl_vector ( wwl_permute( wwl( 1L, 2L, 4L, 5L, 0L,0L,0L,0L ), r ), wwl( 43L,22L,44L,23L, 0L,0L,0L,0L ) );
214 :
215 33210613 : return _mm512_extracti64x4_epi64( wwl_or( wwl_or( t0, t1 ), t2 ), 0 );
216 33210613 : }
217 :
218 : /* fd_r43x6_approx_carry_propagate_limbs(x,y) computes a signed
219 : fd_r43x6_t equivalent to an arbitrary fd_r43x6_t that has been
220 : extracted into the longs x0-x5 and stores the result into the longs
221 : y0-y5. On return:
222 :
223 : y0 in [-19*2^23,2^43+19*(2^23-1))
224 : y1-y4 in [ -2^20,2^43+ (2^20-1))
225 : y5 in [ -2^20,2^40+ (2^20-1))
226 :
227 : In-place operation fine. This macro is robust.
228 :
229 : If x is unsigned or more generally x0-x5 in [0,2^63), the result will
230 : be an unreduced fd_r43x6_t with:
231 :
232 : y0 in [0,2^43+19*(2^23-1))
233 : y1-y4 in [0,2^43+ (2^20-1))
234 : y5 in [0,2^40+ (2^20-1))
235 :
236 : Theory:
237 :
238 : x = <x0,x1,x2,x3,x4,x5>
239 : = <x0l,x1l,x2l,x3l,x4l,x5l> + <2^43*x0h,2^43*x1h,2^43*x2h,2^43*x3h,2^43*x4h,2^40*x5h>
240 : = <x0l,x1l,x2l,x3l,x4l,x5l> + <19*x5h,x0h,x1h,x2h,x3h,x4h>
241 :
242 : where x0h=floor(x0/2^43) and x0l = x0-2^43*x0h and similarly for
243 : x1-x4 while x5h = floor(x5/2^40) and x5l=x5-2^40*x5h.
244 :
245 : Above we used:
246 :
247 : 2^215*2^40*x5h = 2^255*x5h = (p+19)*x5h mod p = 19*x5h mod p.
248 :
249 : Equivalently, x0l-x5l are the least significant {43,43,43,43,43,40}
250 : bits of x0-x5 and x0h-x5l are the sign extending right shifts of
251 : x0-x5 by the same.
252 :
253 : For arbitrary x we have:
254 :
255 : x0l-x4l in [0,2^43), x0h-x4h in [-2^20,2^20)
256 : x5l in [0,2^40), x5h in [-2^23,2^23)
257 :
258 : while for x0-x5 in [0,2^63) (which includes unsigned) we have:
259 :
260 : x0l-x4l in [0,2^43), x0h-x4h in [0,2^20)
261 : x5l in [0,2^40), x5h in [0,2^23)
262 :
263 : This yields the above ranges for y0-y5. There are no intermediate
264 : overflows in the computation.
265 :
266 : This is a building block for more complex mappings where x's limbs
267 : have already been extracted in order to minimize the number of limb
268 : extracts and fd_r43x6 constructs.
269 :
270 : Note that if we used ulongs (and thus zero padding right shifts)
271 : below, this same style calculation could be used on an arbitrary
272 : _ulong_ limbed x. The result would still be an unreduced fd_r43x6_t
273 : with:
274 :
275 : y0-y4 in [0,2^43+19*(2^24-1))
276 : y5 in [0,2^40+ 2^21-1 ) */
277 :
278 103472758 : #define fd_r43x6_approx_carry_propagate_limbs(x,y) do { \
279 103472758 : long const _m43 = (1L<<43)-1L; \
280 103472758 : long const _m40 = (1L<<40)-1L; \
281 103472758 : long _x0 = (x##0); \
282 103472758 : long _x1 = (x##1); \
283 103472758 : long _x2 = (x##2); \
284 103472758 : long _x3 = (x##3); \
285 103472758 : long _x4 = (x##4); \
286 103472758 : long _x5 = (x##5); \
287 103472758 : (y##0) = (_x0 & _m43) + 19L*(_x5>>40); \
288 103472758 : (y##1) = (_x1 & _m43) + (_x0>>43); \
289 103472758 : (y##2) = (_x2 & _m43) + (_x1>>43); \
290 103472758 : (y##3) = (_x3 & _m43) + (_x2>>43); \
291 103472758 : (y##4) = (_x4 & _m43) + (_x3>>43); \
292 103472758 : (y##5) = (_x5 & _m40) + (_x4>>43); \
293 103472758 : } while(0)
294 :
295 : /* fd_r43x6_approx_carry_propagate is a vectorized version of the above.
296 : Returned lanes 6 and 7 are zero. If we've already extracted x's
297 : limbs and/or need the resulting limbs after, it is usually faster to
298 : do the extract and then use the scalar implementation.
299 :
300 : The fold_unsigned variant is the same but assumes x is unsigned or
301 : more generally x0-x5 in [0,2^63). This allows a faster madd52lo to
302 : be used instead of a slower mullo. (Hat tip to Philip Taffet for
303 : pointing this out.) It will also work for an arbitrary limbed ulong
304 : x.
305 :
306 : The fold_signed variant assumes x is signed or more generally:
307 :
308 : x0 in [-2^63+19*2^23,2^63)
309 : x1-x5 in [-2^63 +2^20,2^63)
310 :
311 : and subtracts x by <19*2^23,2^20,2^20,2^20,2^20,2^20> (which will not
312 : overflow) before the approx carry propagate and add it back after.
313 : This will not overflow and will not change the element represented.
314 : But it does yield an unreduced result with limbs:
315 :
316 : y0 in [0,2^43+19*(2^24-1))
317 : y1-y4 in [0,2^43+ (2^21-1))
318 : y5 in [0,2^40+ (2^21-1))
319 :
320 : These variants are particularly useful for mapping results of a
321 : additions and subtractions into unreduced results for subsequent
322 : operations. */
323 :
324 : #if 0 /* A mullo based implementation is slightly slower ... */
325 : #define fd_r43x6_approx_carry_propagate( x ) (__extension__({ \
326 : long const _m43 = (1L<<43)-1L; \
327 : long const _m40 = (1L<<40)-1L; \
328 : wwl_t _x = (x); \
329 : wwl_add( wwl_and( _x, wwl( _m43,_m43,_m43,_m43,_m43,_m40, 0L,0L ) ), \
330 : wwl_mul( wwl( 19L,1L,1L,1L,1L,1L, 0L,0L ), \
331 : wwl_permute( wwl( 5L,0L,1L,2L,3L,4L, 6L,7L ), \
332 : wwl_shr_vector( _x, wwl( 43L,43L,43L,43L,43L,40L, 0L,0L ) ) ) ) ); \
333 : }))
334 : #else /* ... than a more obtuse shift-and-add based implementation */
335 : #define fd_r43x6_approx_carry_propagate( x ) (__extension__({ \
336 : long const _m43 = (1L<<43)-1L; \
337 : long const _m40 = (1L<<40)-1L; \
338 : wwl_t _x = (x); \
339 : wwl_t _xl = wwl_and( _x, wwl( _m43,_m43,_m43,_m43,_m43,_m40, 0L,0L ) ); \
340 : wwl_t _xh = wwl_shr_vector( _x, wwl( 43L,43L,43L,43L,43L,40L, 0L,0L ) ); \
341 : wwl_t _c = wwl_select( wwl( 5L,0L,1L,2L,3L,4L, 8L,8L ), _xh, wwl_zero() ); \
342 : wwl_t _d = wwl_and( wwl_add( wwl_shl( _c, 1 ), wwl_shl( _c, 4 ) ), wwl( -1L,0L,0L,0L,0L,0L, 0L,0L ) ); \
343 : /* _xl = < x0l,x1l,x2l,x3l,x4l,x5l, 0,0> */ \
344 : /* _c = < x5h,x0h,x1h,x2h,x3h,x4h, 0,0> */ \
345 : /* _d = <18*x5h, 0, 0, 0, 0, 0, 0,0> */ \
346 : wwl_add( wwl_add( _xl, _c ), _d ); \
347 : }))
348 : #endif
349 :
350 916911909 : #define fd_r43x6_fold_unsigned( x ) (__extension__({ \
351 916911909 : long const _m43 = (1L<<43)-1L; \
352 916911909 : long const _m40 = (1L<<40)-1L; \
353 916911909 : wwl_t _x = (x); \
354 916911909 : wwl_madd52lo( wwl_and( _x, wwl( _m43,_m43,_m43,_m43,_m43,_m40, 0L,0L ) ), \
355 916911909 : wwl( 19L,1L,1L,1L,1L,1L, 0L,0L ), \
356 916911909 : wwl_permute( wwl( 5L,0L,1L,2L,3L,4L, 6L,7L ), \
357 916911909 : wwl_shru_vector( _x, wwl( 43L,43L,43L,43L,43L,40L, 0L,0L ) ) ) ); \
358 916911909 : }))
359 :
360 3854985 : #define fd_r43x6_fold_signed( x ) (__extension__({ \
361 3854985 : wwl_t const _b = wwl( 19L<<23,1L<<20,1L<<20,1L<<20,1L<<20,1L<<20, 0L,0L ); \
362 3854985 : wwl_add( fd_r43x6_approx_carry_propagate( wwl_sub( (x), _b ) ), _b ); \
363 3854985 : }))
364 :
365 : /* fd_r43x6_biased_carry_propagate_limbs computes an equivalent
366 : fd_r43x6_t to a signed fd_r43x6_t that has been extracted into the
367 : longs x0-x5 and stores the result into the longs y0-y5. x5 is
368 : subtracted by a small bias in [0,2^20] before that is added back
369 : after. This has no impact on the element represented but can impact
370 : the range needed for limb 5. In-place operation fine. This macro is
371 : robust.
372 :
373 : IMPORTANT! THIS SHOULD NOT BE APPLIED TO ARBITRARY X.
374 :
375 : If x is signed or more generally:
376 :
377 : x0 in [-2^63+19*2^23,2^63-19*(2^23-1))
378 : x1-x4 in [-2^63+ 2^20,2^63- (2^20-1))
379 : x5 in [-2^63+b, 2^63 )
380 :
381 : On return y will be signed but only in one limb such that it is
382 : almost nearly reduced:
383 :
384 : y0-y4 in [0, 2^43 )
385 : y5 in [-2^20+b,2^40+2^20-1+b)
386 :
387 : Thus, with b=2^20, if x is signed or more generally
388 :
389 : x0 in [-2^63+19*2^23,2^63-19*(2^23-1))
390 : x1-x4 in [-2^63+ 2^20,2^63- (2^20-1))
391 : x5 in [-2^63+ 2^20,2^63 )
392 :
393 : On return y will be nearly reduced with y5 in [0,2^40+2^21-1).
394 :
395 : And, with b=0, if x is unsigned or more generally:
396 :
397 : x0 in [0,2^63-19*(2^23-1))
398 : x1-x4 in [0,2^63- (2^20-1))
399 : x5 in [0,2^63 )
400 :
401 : On return y will be nearly reduced with y5 in [0,2^40+2^20-1).
402 :
403 : With b=0, the more restricted x is, the tighter the y5 range. If x
404 : is {unreduced,unpacked,nearly reduced}, with b=0, on return, y will
405 : be nearly reduced with y5 in {[0,2^40+2^5-1),[0,2^40+1),[0,2^40+1)}.
406 : If x is reduced, on return, y will be reduced.
407 :
408 : Under the hood, this does a serial carry propagate on the limbs.
409 :
410 : Theory:
411 :
412 : Break an arbitrary x5 into its lower and upper bits as was done for
413 : approx_carry_propagate. Then:
414 :
415 : x = <x0,x1,x2,x3,x4,x5l> + 2^40 <0,0,0,0,0,x5h>
416 : = <x0,x1,x2,x3,x4,x5l> + 19 <x5h,0,0,0,0,0>
417 : = <x0l+19*x5h,x1,x2,x3,x4,x5l>
418 :
419 : As x5h will be in [-2^23,2^23), if the initial x0 is in
420 : [-2^63+19*2^23,2^63-19*(2^23-1)), this new representation can be
421 : computed without intermediate overflow with limb 4 in [0,2^40) and
422 : limb 0 in [-2^63,2^63).
423 :
424 : Now break an arbitrary x0 into its lower and upper bits. Then:
425 :
426 : x = <x0,x1,x2,x3,x4,x5>
427 : = <x0l,x1,x2,x3,x4,x5> + 2^43 <x0h,0,0,0,0,0>
428 : = <x0l,x1,x2,x3,x4,x5> + <0,x0h,0,0,0,0>
429 : = <x0l,x1+x0h,x2,x3,x5>
430 :
431 : As x0h will be in [-2^20,2^20), if the initial x1 is in
432 : [-2^63+2^20,2^63-(2^20-1)), this new representation can be computed
433 : without intermediate overflow with limb 0 in [0,2^43) and limb 1 in
434 : [-2^63,2^63).
435 :
436 : We can similarly serially propagate limb 1's carries to 2, 2 to 3, 3
437 : to 4 and 4 to 5. As x4h will be in [-2^20,2^20), the limb 5's final
438 : value will be in [-2^20,2^40+2^20-1).
439 :
440 : If x is in unsigned or in the unsigned range described above, limb 4's
441 : carry will be in [0,2^20) such that the result will be nearly reduced
442 : with limb 5 in [0,2^40+2^20-1).
443 :
444 : If x is {unreduced,unpacked,nearly reduced}, limb 4's carry will be
445 : in {[0,2^5),[0,1],[0,1]} such that limb 5 will be be in
446 : {[0,2^40+2^5-1),[0,2^40+1),[0,2^40+1)}.
447 :
448 : If x was reduced, all carries will be zero such that y will have the
449 : same limbs as x.
450 :
451 : This yields the above.
452 :
453 : Like approx_carry_propagate, this is a building block for more
454 : complex mappings where x's limbs have already been extracted in order
455 : to minimize the number of limb extracts and fd_r43x6 constructs. */
456 :
457 265669992 : #define fd_r43x6_biased_carry_propagate_limbs(x,y,b) do { \
458 265669992 : long const _m43 = (1L<<43)-1L; \
459 265669992 : long const _m40 = (1L<<40)-1L; \
460 265669992 : long _y0 = (x##0); \
461 265669992 : long _y1 = (x##1); \
462 265669992 : long _y2 = (x##2); \
463 265669992 : long _y3 = (x##3); \
464 265669992 : long _y4 = (x##4); \
465 265669992 : long _y5 = (x##5); \
466 265669992 : long _b = (b); \
467 265669992 : long _c; \
468 265669992 : _y5 -= _b; \
469 265669992 : _c = _y5>>40; _y5 &= _m40; _y0 += 19L*_c; \
470 265669992 : _c = _y0>>43; _y0 &= _m43; _y1 += _c; \
471 265669992 : _c = _y1>>43; _y1 &= _m43; _y2 += _c; \
472 265669992 : _c = _y2>>43; _y2 &= _m43; _y3 += _c; \
473 265669992 : _c = _y3>>43; _y3 &= _m43; _y4 += _c; \
474 265669992 : _c = _y4>>43; _y4 &= _m43; _y5 += _c; \
475 265669992 : _y5 += _b; \
476 265669992 : (y##0) = _y0; \
477 265669992 : (y##1) = _y1; \
478 265669992 : (y##2) = _y2; \
479 265669992 : (y##3) = _y3; \
480 265669992 : (y##4) = _y4; \
481 265669992 : (y##5) = _y5; \
482 265669992 : } while(0)
483 :
484 : /* Note: fd_r43x6_biased_carry_propagate_limbs does not have a good
485 : AVX-512 implementation (it is highly sequential). */
486 :
487 : /* fd_r43x6_mod_nearly_reduced_limbs computes the reduced fd_r43x6_t for
488 : a nearly reduced fd_r43x6_t that has been extracted into the longs
489 : x0-x5 and stores the limbs in the longs y0-y5. In-place operation
490 : fine. This macro is robust. Theory:
491 :
492 : Let x = q p + r where q is an integer and r is in [0,p). Since x is
493 : in [0,2*p), q is in [0,1]. If x<p, q=0 and r=x, otherwise, q=1 and
494 : r=x-p. Using p = 2^255 - 19, x<p implies x+19<2^255. Thus, if x+19
495 : has bit 255 (limb 5 bit 40) clear, r=x, otherwise, r=x-p.
496 :
497 : In pseudo code:
498 : y = x + 19 ... < 2*p+19 = 2^256-19 < 2^256
499 : q = y>>255 ... in [0,1]
500 : if !q, r = x ... x in [0,p ) -> r in [0,p)
501 : else r = x - (2^255-19) ... x in [p,2*p) -> r in [0,p)
502 :
503 : Simplifying:
504 : y = x + 19
505 : q = y>>255
506 : if !q, r = y - 19
507 : else r = y - 2^255
508 :
509 : Or:
510 : y = x + 19
511 : q = y>>255
512 : y -= q<<255 ... clear bit 255
513 : if !q, r = y - 19
514 : else r = y
515 :
516 : Or, branchless for deterministic performance:
517 : y = x + 19
518 : q = y>>255
519 : y -= q<<255
520 : r = y - if(!q,19,0) */
521 :
522 125669592 : #define fd_r43x6_mod_nearly_reduced_limbs(x,y) do { \
523 125669592 : long const _m43 = (1L<<43)-1L; \
524 125669592 : long const _m40 = (1L<<40)-1L; \
525 125669592 : long _y0 = (x##0); \
526 125669592 : long _y1 = (x##1); \
527 125669592 : long _y2 = (x##2); \
528 125669592 : long _y3 = (x##3); \
529 125669592 : long _y4 = (x##4); \
530 125669592 : long _y5 = (x##5); \
531 125669592 : long _c; \
532 125669592 : \
533 125669592 : /* y = x + 19, q = y>>255, y -= q<<255 */ \
534 125669592 : _y0 += 19L; \
535 125669592 : _c = _y0 >> 43; _y0 &= _m43; _y1 += _c; \
536 125669592 : _c = _y1 >> 43; _y1 &= _m43; _y2 += _c; \
537 125669592 : _c = _y2 >> 43; _y2 &= _m43; _y3 += _c; \
538 125669592 : _c = _y3 >> 43; _y3 &= _m43; _y4 += _c; \
539 125669592 : _c = _y4 >> 43; _y4 &= _m43; _y5 += _c; \
540 125669592 : _c = _y5 >> 40; _y5 &= _m40; \
541 125669592 : \
542 125669592 : /* r = y - if(!q,19,0) */ \
543 125669592 : _y0 -= fd_long_if( !_c, 19L, 0L ); \
544 125669592 : _c = _y0 >> 43; _y0 &= _m43; _y1 += _c; \
545 125669592 : _c = _y1 >> 43; _y1 &= _m43; _y2 += _c; \
546 125669592 : _c = _y2 >> 43; _y2 &= _m43; _y3 += _c; \
547 125669592 : _c = _y3 >> 43; _y3 &= _m43; _y4 += _c; \
548 125669592 : _c = _y4 >> 43; _y4 &= _m43; _y5 += _c; \
549 125669592 : \
550 125669592 : (y##0) = _y0; \
551 125669592 : (y##1) = _y1; \
552 125669592 : (y##2) = _y2; \
553 125669592 : (y##3) = _y3; \
554 125669592 : (y##4) = _y4; \
555 125669592 : (y##5) = _y5; \
556 125669592 : } while(0)
557 :
558 : /* Note: fd_r43x6_mod_nearly_reduced_limbs does not have a good AVX-512
559 : implementation (it is highly sequential). */
560 :
561 : /* fd_r43x6_approx_mod(x) returns a nearly reduced fd_r43x6_t equivalent
562 : to an arbitrary fd_r43x6_t x. On return y5 will be in [0,2^40+2).
563 :
564 : fd_r43x6_approx_mod_signed(x) does the same for signed x or, more
565 : generally:
566 :
567 : x0 in [-2^63+19*2^23,2^63-19*(2^23-1))
568 : x1-x4 in [-2^63+ 2^20,2^63- (2^20-1))
569 : x5 in [-2^63+ 2^20,2^63 )
570 :
571 : fd_r43x6_approx_mod_unsigned(x) does the same for unsigned x or, more
572 : generally:
573 :
574 : x0 in [0,2^63-19*(2^23-1))
575 : x1-x4 in [0,2^63- (2^20-1))
576 : x5 in [0,2^63 )
577 :
578 : On return y5 will be in [0,2^40+2^20-1).
579 :
580 : fd_r43x6_approx_mod_unreduced(x) does the same for unreduced x. On
581 : return y5 will be in [0,2^40+2^5-1).
582 :
583 : fd_r43x6_approx_mod_unpacked(x) does the same for unpacked x. On
584 : return y5 will be in [0,2^40+1). */
585 :
586 : FD_FN_UNUSED FD_FN_CONST static fd_r43x6_t /* Work around -Winline */
587 80000100 : fd_r43x6_approx_mod( fd_r43x6_t x ) {
588 80000100 : long y0, y1, y2, y3, y4, y5;
589 80000100 : fd_r43x6_extract_limbs( x, y );
590 :
591 : /* At this point y is arbitrary. We do an approx carry propagate to
592 : reduce the range of limbs suitable for a biased carry propagate.
593 : (Note: it is faster here to do extract then approx-cp than
594 : vector-approx-cp then extract.) */
595 :
596 80000100 : fd_r43x6_approx_carry_propagate_limbs( y, y );
597 :
598 : /* At this point y has:
599 :
600 : y0 in [-19*2^23,2^43+19*(2^23-1))
601 : y1-y4 in [ -2^20,2^43+ (2^20-1))
602 : y5 in [ -2^20,2^40+ (2^20-1))
603 :
604 : An unbiased carry propagate could produce y5=-1. So, we use a
605 : biased c-p with a b=1. TODO: CONSIDER JUST USING 2^20 BIAS ALWAYS? */
606 :
607 80000100 : fd_r43x6_biased_carry_propagate_limbs( y, y, 1L );
608 :
609 80000100 : return fd_r43x6( y0, y1, y2, y3, y4, y5 );
610 80000100 : }
611 :
612 : FD_FN_UNUSED FD_FN_CONST static fd_r43x6_t /* Work around -Winline */
613 20000100 : fd_r43x6_approx_mod_signed( fd_r43x6_t x ) {
614 20000100 : long y0, y1, y2, y3, y4, y5;
615 20000100 : fd_r43x6_extract_limbs( x, y );
616 :
617 : /* At this point y has:
618 :
619 : x0 in [-2^63+19*2^23,2^63-19*(2^23-1))
620 : x1-x4 in [-2^63+ 2^20,2^63- (2^20-1))
621 : x5 in [-2^63+ 2^20,2^63 )
622 :
623 : so we can do a biased carry propagate y with b=2^20 as described
624 : above. */
625 :
626 20000100 : fd_r43x6_biased_carry_propagate_limbs( y, y, 1L<<20 );
627 :
628 20000100 : return fd_r43x6( y0, y1, y2, y3, y4, y5 );
629 20000100 : }
630 :
631 : FD_FN_UNUSED FD_FN_CONST static fd_r43x6_t /* Work around -Winline */
632 60000300 : fd_r43x6_approx_mod_unsigned( fd_r43x6_t x ) {
633 60000300 : long y0, y1, y2, y3, y4, y5;
634 60000300 : fd_r43x6_extract_limbs( x, y );
635 :
636 : /* At this point y has:
637 :
638 : x0 in [0,2^63-19*(2^23-1))
639 : x1-x4 in [0,2^63- (2^20-1))
640 : x5 in [0,2^63 )
641 :
642 : so we can do an unbiased carry propagate as described above. */
643 :
644 60000300 : fd_r43x6_biased_carry_propagate_limbs( y, y, 0L );
645 :
646 60000300 : return fd_r43x6( y0, y1, y2, y3, y4, y5 );
647 60000300 : }
648 :
649 : #define fd_r43x6_approx_mod_unreduced fd_r43x6_approx_mod_unsigned /* no difference in impl, tighter y5 result described above */
650 : #define fd_r43x6_approx_mod_unpacked fd_r43x6_approx_mod_unsigned /* no difference in impl, tighter y5 result described above */
651 :
652 : /* fd_r43x6_mod(x) returns the reduced fd_r43x6_t equivalent to an
653 : arbitrary fd_r43x6_t x.
654 :
655 : fd_r43x6_approx_mod_signed(x) does the same for signed x or more
656 : generally:
657 :
658 : x0 in [-2^63+19*2^23,2^63-19*(2^23-1))
659 : x1-x4 in [-2^63+ 2^20,2^63- (2^20-1))
660 : x5 in [-2^63+ 2^20,2^63 )
661 :
662 : fd_r43x6_mod_unreduced(x) does the same for unreduced x, or, more
663 : generally:
664 :
665 : x0 in [0,2^63-19*(2^23-1))
666 : x1-x4 in [0,2^63- (2^20-1))
667 : x5 in [0,2^63 )
668 :
669 : fd_r43x6_mod_unpacked(x) does the same for unpacked x.
670 :
671 : fd_r43x6_mod_nearly_reduced(x) does the same for nearly reduced x. */
672 :
673 : FD_FN_UNUSED FD_FN_CONST static fd_r43x6_t /* Work around -Winline */
674 23472658 : fd_r43x6_mod( fd_r43x6_t x ) {
675 23472658 : long y0, y1, y2, y3, y4, y5;
676 23472658 : fd_r43x6_extract_limbs( x, y );
677 23472658 : fd_r43x6_approx_carry_propagate_limbs( y, y );
678 23472658 : fd_r43x6_biased_carry_propagate_limbs( y, y, 1L );
679 : /* At this point, x is nearly reduced */
680 23472658 : fd_r43x6_mod_nearly_reduced_limbs( y, y );
681 23472658 : return fd_r43x6( y0, y1, y2, y3, y4, y5 );
682 23472658 : }
683 :
684 : FD_FN_UNUSED FD_FN_CONST static fd_r43x6_t /* Work around -Winline */
685 20000101 : fd_r43x6_mod_signed( fd_r43x6_t x ) {
686 20000101 : long y0, y1, y2, y3, y4, y5;
687 20000101 : fd_r43x6_extract_limbs( x, y );
688 20000101 : fd_r43x6_biased_carry_propagate_limbs( y, y, 1L<<20 );
689 : /* At this point, x is nearly reduced */
690 20000101 : fd_r43x6_mod_nearly_reduced_limbs( y, y );
691 20000101 : return fd_r43x6( y0, y1, y2, y3, y4, y5 );
692 20000101 : }
693 :
694 : FD_FN_UNUSED FD_FN_CONST static fd_r43x6_t /* Work around -Winline */
695 60000303 : fd_r43x6_mod_unsigned( fd_r43x6_t x ) {
696 60000303 : long y0, y1, y2, y3, y4, y5;
697 60000303 : fd_r43x6_extract_limbs( x, y );
698 60000303 : fd_r43x6_biased_carry_propagate_limbs( y, y, 0L );
699 : /* At this point, x is nearly reduced */
700 60000303 : fd_r43x6_mod_nearly_reduced_limbs( y, y );
701 60000303 : return fd_r43x6( y0, y1, y2, y3, y4, y5 );
702 60000303 : }
703 :
704 : #define fd_r43x6_mod_unreduced fd_r43x6_mod_unsigned /* no difference in impl */
705 : #define fd_r43x6_mod_unpacked fd_r43x6_mod_unsigned /* no difference in impl */
706 :
707 : FD_FN_UNUSED FD_FN_CONST static fd_r43x6_t /* Work around -Winline */
708 20000100 : fd_r43x6_mod_nearly_reduced( fd_r43x6_t x ) {
709 20000100 : long y0, y1, y2, y3, y4, y5;
710 20000100 : fd_r43x6_extract_limbs( x, y );
711 : /* At this point, x is already nearly reduced */
712 20000100 : fd_r43x6_mod_nearly_reduced_limbs( y, y );
713 20000100 : return fd_r43x6( y0, y1, y2, y3, y4, y5 );
714 20000100 : }
715 :
716 : /* fd_r43x6_neg_fast(x) returns z = -x, computed as z = p - x
717 : fd_r43x6_add_fast(x,y) returns z = x + y
718 : fd_r43x6_sub_fast(x,y) returns z = x - y, computed as z = x + (p - y)
719 :
720 : These will be applied to lanes 6 and 7 and these assume that the
721 : corresponding limbs of x and y when added / subtracted will produce a
722 : result that doesn't overflow the range of a long.
723 :
724 : For example, it is safe to add a large (but bounded) number of
725 : unpacked x to produce an unreduced sum and then do a single mod at
726 : the end to produce the reduced result. With 0 < ll <= mm < 63 and
727 : letting nn = mm+1, given the input ranges, the below give
728 : conservative output ranges.
729 :
730 : -ull -> sll
731 : -sll -> sll
732 :
733 : ull + umm -> unn, umm + ull -> unn
734 : ull + smm -> snn, umm + sll -> snn
735 : sll + umm -> snn, smm + ull -> snn
736 : sll + smm -> snn, smm + sll -> snn
737 :
738 : ull - umm -> smm, umm - ull -> smm
739 : ull - smm -> snn, umm - sll -> snn
740 : sll - umm -> snn, smm - ull -> snn
741 : sll - smm -> snn, smm - sll -> snn */
742 :
743 1001743 : #define fd_r43x6_neg_fast( x ) wwl_sub( fd_r43x6_p(), (x) )
744 17946075 : #define fd_r43x6_add_fast( x, y ) wwl_add( (x), (y) )
745 1113024 : #define fd_r43x6_sub_fast( x, y ) wwl_add( (x), wwl_sub( (fd_r43x6_p()), (y) ) )
746 :
747 : /* fd_r43x6_mul_fast(x,y) returns z = x*y as an unsigned fd_r43x6_t
748 : with lanes 6 and 7 zero where x and y are unreduced fd_r43x6_t's
749 : (i.e. in u47). Ignores lanes 6 and 7 of x and assumes lanes 6 and 7
750 : of y are zero. More specifically, u44/u45/u46/u47 inputs produce a
751 : u62/u62/u62/u63 output. */
752 :
753 : FD_FN_UNUSED FD_FN_CONST static fd_r43x6_t /* Work around -Winline */
754 : fd_r43x6_mul_fast( fd_r43x6_t x,
755 152051062 : fd_r43x6_t y ) {
756 :
757 : /* 5x5 grade school-ish multiplication accelerated with AVX512 integer
758 : madd52 instructions. The basic algorithm is:
759 :
760 : x*y = (sum_i xi 2^(43*i))*(sum_j yj 2^(43*j))
761 : = sum_i sum_j xi*xj 2^(43*(i+j))
762 : = sum_i sum_j (pijl + 2^43 pijh) 2^(43*(i+j))
763 : = sum_i sum_j ( pijl 2^(43*(i+j)) + pijh 2^(43*(i+j+1)) )
764 : = sum_k zk 2^43 k
765 :
766 : where the product of xi*xj has been split such that:
767 :
768 : pijl + 2^43 pijh = xi*xj
769 :
770 : and zk has grouped all terms with the same scale factor:
771 :
772 : zk = sum_i sum_j ( pijl ((i+j)==k) + pijh ((i+j+1)==k) )
773 :
774 : Or graphically:
775 :
776 : x5 x4 x3 x2 x1 x0
777 : x y5 y4 y3 y2 y1 y0
778 : --------------------------------
779 : p50l p40l p30l p20l p10l p00l -> t0
780 : p50h p40h p30h p20h p10h p00h \
781 : p51l p41l p31l p21l p11l p01l / t1
782 : p51h p41h p31h p21h p11h p01h \
783 : p52l p42l p32l p22l p12l p02l / t2
784 : p52h p42h p32h p22h p12h p02h \
785 : p53l p43l p33l p23l p13l p03l / t3
786 : p53h p43h p33h p23h p13h p03h \
787 : p54l p44l p34l p24l p14l p04l / t4
788 : p54h p44h p34h p24h p14h p04h \
789 : p55l p45l p35l p25l p15l p05l / t5
790 : p55h p45h p35h p25h p15h p05h -> t6
791 : -----------------------------------------------------------
792 : z11 z10 z9 z8 z7 z6 z5 z4 z3 z2 z1 z0
793 : \----------------/ \-----------------------------------/
794 : zh zl
795 :
796 : The conventional split would require xi and xj to be in [0,2^43) and
797 : yield pijl and pijh in [0,2^43). But we need to use a different
798 : split to exploit the madd52 instructions:
799 :
800 : ul = madd52lo(al,x,y) = LO64( al + LO52( LO52(x)*LO52(y) ) )
801 : uh = madd52hi(ah,x,y) = LO64( ah + HI52( LO52(x)*LO52(y) ) )
802 :
803 : Consider when al and ah are zero. Since x and y here are unreduced
804 : and thus in [0,2^47), we have:
805 :
806 : ul = LO52( x*y )
807 : uh = HI52( x*y )
808 : --> ul + 2^52 uh = x*y
809 : --> ul + 2^43 (2^9 uh) = x*y
810 :
811 : Thus, we can use pl=ul and and ph=2^9 uh from these instructions as
812 : a split for the above. With this we have:
813 :
814 : pl in [0,2^52)
815 : ph in [0,2^51)
816 :
817 : so:
818 :
819 : z{0,1,2,3, 4, 5} < { 2, 5, 8,11,14,17} 2^51
820 : z{6,7,8,9,10,11} < {16,13,10, 7, 4, 1} 2^51
821 :
822 : Note: the [0,2^47) range for unreduced fd_r43x6_t was picked to
823 : yield pl and ph with similar ranges while allowing for fast
824 : reduction below.
825 :
826 : Note: It might seem even better to use a 5 long radix 52 limb
827 : representation such that madd naturally produces the desired
828 : splitting. If the only consideration is the above, this is correct.
829 :
830 : But this calculation is frequently used in long sequential chains
831 : (e.g. the repeated squarings done to compute the multiplicative
832 : inverse). In the 52x5 case, the zk reduction required to produce an
833 : output representation that can be fed into the next multiplication
834 : has no "headroom". All carries from limbs 0-3 must be fully
835 : propagated to limb 4 such that all limbs are in [0,2^52) to be able
836 : to use madd52 subsequent multiply operations. This requires then
837 : extracting all the limbs and doing a slow sequential calculation as
838 : part of the zk reduction. This throws away most of the advantage of
839 : using instructions like madd52 in the first place.
840 :
841 : Using 43x6 is nearly the same cost as 52x5 for the above because we
842 : have unused AVX512 lanes and the scaling needed to tweak the
843 : madd52hi result is a fast shift operation. Critically, the needed
844 : zk reduction (described below) can be done with a fast approximate
845 : carry propagate to get a result that can be immediately fed into
846 : subsequent multiplications.
847 :
848 : The overall impact is that this is typically ~2-3x faster than, for
849 : example, the fd_ed25519_fe_t scalar multiplier on platforms with
850 : AVX512 support.
851 :
852 : This implementation is not the "textbook" style found in the
853 : literature. The textbook implementations accumulate zh and zl by
854 : interleaving alignr with the madds. The putative benefit of such is
855 : that it can make use of the madd52hi adder to save some adds over
856 : the below. Unfortunately, such requires more alignr / more
857 : instruction footprint / more sequential dependencies and has less
858 : ILP. In practice, adds are much cheaper than alignr (remember, data
859 : motion has been more expensive than computation for decades). So
860 : spending some adds to buy fewer alignr, a smaller instruction
861 : footprint and more ILP is a great trade and yields the faster than
862 : textbook implementation below. */
863 :
864 152051062 : wwl_t const zero = wwl_zero();
865 :
866 152051062 : wwl_t x0 = wwl_permute( zero, x );
867 152051062 : wwl_t x1 = wwl_permute( wwl_one(), x );
868 152051062 : wwl_t x2 = wwl_permute( wwl_bcast( 2L ), x );
869 152051062 : wwl_t x3 = wwl_permute( wwl_bcast( 3L ), x );
870 152051062 : wwl_t x4 = wwl_permute( wwl_bcast( 4L ), x );
871 152051062 : wwl_t x5 = wwl_permute( wwl_bcast( 5L ), x );
872 :
873 152051062 : wwl_t t0 = wwl_madd52lo( zero, x0, y );
874 152051062 : wwl_t t1 = wwl_madd52lo( wwl_shl( wwl_madd52hi( zero, x0, y ), 9 ), x1, y );
875 152051062 : wwl_t t2 = wwl_madd52lo( wwl_shl( wwl_madd52hi( zero, x1, y ), 9 ), x2, y );
876 152051062 : wwl_t t3 = wwl_madd52lo( wwl_shl( wwl_madd52hi( zero, x2, y ), 9 ), x3, y );
877 152051062 : wwl_t t4 = wwl_madd52lo( wwl_shl( wwl_madd52hi( zero, x3, y ), 9 ), x4, y );
878 152051062 : wwl_t t5 = wwl_madd52lo( wwl_shl( wwl_madd52hi( zero, x4, y ), 9 ), x5, y );
879 152051062 : wwl_t t6 = wwl_shl( wwl_madd52hi( zero, x5, y ), 9 );
880 :
881 152051062 : wwl_t p0j = t0; /* note: q0j = 0 */
882 152051062 : wwl_t p1j = wwl_slide( zero, t1, 7 ); /* note: q1j = 0 */
883 152051062 : wwl_t p2j = wwl_slide( zero, t2, 6 ); /* note: q2j = 0 */
884 152051062 : wwl_t p3j = wwl_slide( zero, t3, 5 ); wwl_t q3j = wwl_slide( t3, zero, 5 );
885 152051062 : wwl_t p4j = wwl_slide( zero, t4, 4 ); wwl_t q4j = wwl_slide( t4, zero, 4 );
886 152051062 : wwl_t p5j = wwl_slide( zero, t5, 3 ); wwl_t q5j = wwl_slide( t5, zero, 3 );
887 152051062 : wwl_t p6j = wwl_slide( zero, t6, 2 ); wwl_t q6j = wwl_slide( t6, zero, 2 );
888 :
889 152051062 : wwl_t zl = wwl_add( wwl_add( wwl_add( p0j, p1j ), wwl_add( p2j, p3j ) ), wwl_add( wwl_add( p4j, p5j ), p6j ) );
890 152051062 : wwl_t zh = wwl_add( wwl_add( q3j, q4j ), wwl_add( q5j, q6j ) );
891 :
892 : /* At this point:
893 : z = <zl0,zl1,zl2,zl3,zl4,zl5,zl6,zl7> + 2^344 <zh0,zh1,zh2,zh3,0,0,0,0> */
894 :
895 152051062 : wwl_t za = wwl_and( zl, wwl( -1L,-1L,-1L,-1L,-1L,-1L, 0L,0L ) );
896 152051062 : wwl_t zb = wwl_slide( zl, zh, 6 );
897 :
898 : /* At this point:
899 :
900 : z = <za0,za1,za2,za3,za4,za5> + 2^258 <zb0,zb1,zb2,zb3,zb4,zb5>
901 :
902 : where (as shown above):
903 :
904 : za{0,1,2,3,4,5} < 2^51 { 2, 5, 8,11,14,17}
905 : zb{0,1,2,3,4,5) < 2^51 {16,13,10, 7, 4, 1}
906 :
907 : Using:
908 :
909 : 2^258 mod p = (p+19) 2^3 mod p = 19*2^3 = 152
910 :
911 : we can reduce this to 6 limbs via:
912 :
913 : z = <za0,za1,za2,za3,za4,za5> + 152 <zb0,zb1,zb2,zb3,zb4,zb5>
914 :
915 : We can do the sum directly because:
916 :
917 : z{0,1,2,3,4,5} < 2^51 {2434,1981,1528,1075,622,169} < 2^63
918 :
919 : (These limbs are too large to use in a subsequent multiply but they
920 : are in the range where we can do a fd_r43x6_fold_unsigned,
921 : yielding:
922 :
923 : z0 in [0,2^43+19*(2^23-1))
924 : z1-z4 in [0,2^43+ (2^20-1))
925 : z5 in [0,2^40+ (2^20-1))
926 :
927 : This is an unreduced fd_r43x6_t and thus suitable direct use in
928 : subsequent multiply operations. See fd_r43x6_mul.)
929 :
930 : Note that mullo is slow. Since 152 = 2^7 + 2^4 + 2^3 and is
931 : applied to all lanes, we can compute za+152*zb slightly faster via
932 : shift and add techniques. */
933 :
934 152051062 : return wwl_add( wwl_add( za, wwl_shl( zb, 7 ) ), wwl_add( wwl_shl( zb, 4 ), wwl_shl( zb, 3 ) ) );
935 152051062 : }
936 :
937 : /* fd_r43x6_sqr_fast(x) returns z = x^2 as an unsigned fd_r43x6_t with
938 : lanes 6 and 7 zero where x is an unreduced fd_r43x6_t (i.e. in u47).
939 : Assumes lanes 6 and 7 of x are zero. More specifically,
940 : u44/u45/u46/u47 inputs produce a u61/u61/u62/u62 output.
941 :
942 : IMPORTANT! z may not be the same representation returned by
943 : fd_r43x6_mul_fast(x,x). This is because doubling of the off-diagonal
944 : partial products is done _before_ the multiplications while it is
945 : done _after_ the multiplications in fd_r43x6_mul. This is faster for
946 : sqr and reduces the range of the outputs slightly (which can then be
947 : used to further optimize code using sqr). */
948 :
949 : FD_FN_UNUSED FD_FN_CONST static fd_r43x6_t /* Work around -Winline */
950 851142648 : fd_r43x6_sqr_fast( fd_r43x6_t x ) {
951 851142648 : wwl_t const zero = wwl_zero();
952 :
953 : /* The goal of this implementation is to compute each product once, but
954 : to make sure each product gets generated in the right AVX lane so
955 : that we don't have to do a ton of shuffling at the end. In
956 : exchange, we do a lot of permutevars upfront to get everything
957 : setup. It's possible trading permutevars for madd52s might improve
958 : performance.
959 :
960 : We'll compute
961 : p0 = x{0,0,0,0,0,0,3,3} * x{0,1,2,3,4,5,3,4}
962 : p1 = x{4,4,1,1,1,1,1,-} * x{4,5,1,2,3,4,5,-}
963 : p2 = x{3,-,5,-,2,2,2,2} * x{5,-,5,-,2,3,4,5}
964 : with the scaling of non-square terms omitted above. A dash
965 : indicates a don't care value, since we have to do 24
966 : multiplications but there are only 21 unique terms.
967 :
968 : All the terms of p0 belong in the low final result, but the first
969 : two terms of p1, and the first and third terms of p2 belong in the
970 : high final result. The other gotcha is that each multiplication
971 : has a high and a low component, so we confusingly have two
972 : different notions of high/low. */
973 :
974 851142648 : wwl_t x0 = wwl_permute( wwl( 0L, 0L, 0L, 0L, 0L, 0L, 3L, 3L ), x );
975 851142648 : wwl_t x1 = wwl_permute( wwl( 0L, 1L, 2L, 3L, 4L, 5L, 3L, 4L ), x );
976 851142648 : wwl_t x2 = wwl_permute( wwl( 4L, 4L, 1L, 1L, 1L, 1L, 1L, 7L ), x );
977 851142648 : wwl_t x3 = wwl_permute( wwl( 4L, 5L, 1L, 2L, 3L, 4L, 5L, 7L ), x );
978 851142648 : wwl_t x4 = wwl_permute( wwl( 3L, 7L, 5L, 7L, 2L, 2L, 2L, 2L ), x );
979 851142648 : wwl_t x5 = wwl_permute( wwl( 5L, 7L, 5L, 7L, 2L, 3L, 4L, 5L ), x );
980 :
981 : /* Double the non-square terms. */
982 :
983 851142648 : x0 = wwl_shl_vector( x0, wwl( 0L, 1L, 1L, 1L, 1L, 1L, 0L, 1L ) );
984 851142648 : x2 = wwl_shl_vector( x2, wwl( 0L, 1L, 0L, 1L, 1L, 1L, 1L, 1L ) );
985 851142648 : x4 = wwl_shl_vector( x4, wwl( 1L, 1L, 0L, 1L, 0L, 1L, 1L, 1L ) );
986 :
987 851142648 : wwl_t p0l = wwl_madd52lo( zero, x0, x1 );
988 851142648 : wwl_t p1l = wwl_madd52lo( zero, x2, x3 );
989 851142648 : wwl_t p2l = wwl_madd52lo( zero, x4, x5 );
990 :
991 : /* Use the same approach as in the multiply to generate the high bits
992 : of each individual product. */
993 :
994 851142648 : wwl_t p0h = wwl_shl( wwl_madd52hi( zero, x0, x1 ), 9 );
995 851142648 : wwl_t p1h = wwl_shl( wwl_madd52hi( zero, x2, x3 ), 9 );
996 851142648 : wwl_t p2h = wwl_shl( wwl_madd52hi( zero, x4, x5 ), 9 );
997 :
998 : /* Generate masks to split p_i into the terms that belong in the high
999 : word and low word. */
1000 :
1001 851142648 : wwl_t const mask1 = wwl( -1L,-1L, 0L, 0L, 0L, 0L, 0L,0L );
1002 851142648 : wwl_t const mask2 = wwl( -1L, 0L,-1L, 0L, 0L, 0L, 0L,0L );
1003 851142648 : wwl_t zll = wwl_add( p0l, wwl_add( wwl_andnot( mask1, p1l ), wwl_andnot( mask2, p2l ) ) );
1004 851142648 : wwl_t zlh = wwl_add( p0h, wwl_add( wwl_andnot( mask1, p1h ), wwl_andnot( mask2, p2h ) ) );
1005 851142648 : wwl_t zhl = wwl_add( wwl_and ( mask1, p1l ), wwl_and ( mask2, p2l ) );
1006 851142648 : wwl_t zhh = wwl_add( wwl_and ( mask1, p1h ), wwl_and ( mask2, p2h ) );
1007 :
1008 : /* Generate zl and zh as in fd_r43x6_mul_fast */
1009 :
1010 851142648 : wwl_t zl = wwl_add( zll, wwl_slide( zero, zlh, 7 ) );
1011 851142648 : wwl_t zh = wwl_add( zhl, wwl_add( wwl_slide( zero, zhh, 7 ), wwl_slide( zlh, zero, 7 ) ) );
1012 :
1013 851142648 : wwl_t za = wwl_and( zl, wwl( -1L,-1L,-1L,-1L,-1L,-1L, 0L,0L ) );
1014 851142648 : wwl_t zb = wwl_slide( zl, zh, 6 );
1015 :
1016 : /* By the same type of analysis above, we can still do the sum
1017 : directly as:
1018 :
1019 : z{0,1,2,3,4,5} < 2^51 {1826,1371,1222,767,618,163} < 2^62
1020 :
1021 : (Note that the result is fits into u62 instead of u63 like for
1022 : mul_fast.) */
1023 :
1024 851142648 : return wwl_add( wwl_add( za, wwl_shl( zb, 7 ) ), wwl_add( wwl_shl( zb, 4 ), wwl_shl( zb, 3 ) ) );
1025 851142648 : }
1026 :
1027 : /* fd_r43x6_scale_fast(x0,y) returns z = <x0,0,0,0,0>*y as an unsigned
1028 : fd_r43x6_t with lanes 6 and 7 zero where x is in [0,2^47) and y is an
1029 : unreduced fd_r43x6_t (i.e. in u47). Assumes lanes 6 and 7 of y are
1030 : zero. More specifically, u47 inputs produce a u59 output. */
1031 :
1032 : FD_FN_CONST static inline fd_r43x6_t
1033 : fd_r43x6_scale_fast( long _x0,
1034 46740185 : fd_r43x6_t y ) {
1035 :
1036 : /* This is fd_r43x6_mul with x = <_x0,0,0,0,0> and zeros values
1037 : optimized out. See fd_r43x6_mul for detailed explanation how this
1038 : works. */
1039 :
1040 46740185 : wwl_t const zero = wwl_zero();
1041 :
1042 46740185 : wwl_t x0 = wwl_bcast( _x0 );
1043 :
1044 46740185 : wwl_t t0 = wwl_madd52lo( zero, x0, y );
1045 46740185 : wwl_t t1 = wwl_shl( wwl_madd52hi( zero, x0, y ), 9 );
1046 :
1047 46740185 : wwl_t p0j = t0;
1048 46740185 : wwl_t p1j = wwl_slide( zero, t1, 7 );
1049 :
1050 46740185 : wwl_t zl = wwl_add( p0j, p1j );
1051 :
1052 46740185 : wwl_t za = wwl_and( zl, wwl( -1L,-1L,-1L,-1L,-1L,-1L, 0L,0L ) );
1053 46740185 : wwl_t zb = wwl_slide( zl, zero, 6 );
1054 :
1055 : /* At this point:
1056 :
1057 : za{0,1,2,3,4,5} < 2^51 {2,3,3,3,3,3}
1058 : zb0 < 2^51
1059 :
1060 : such that:
1061 :
1062 : z{0,1,2,3,4,5} < 2^51 {154,3,3,3,3,3} < 2^63 */
1063 :
1064 46740185 : return wwl_add( wwl_add( za, wwl_shl( zb, 7 ) ), wwl_add( wwl_shl( zb, 4 ), wwl_shl( zb, 3 ) ) );
1065 46740185 : }
1066 :
1067 : /* fd_r43x6_neg/add/sub/mul/scale fold the results of their fast
1068 : counterparts above. Given unreduced r43x6_t's (i.e. in u47) with
1069 : lanes 6 and 7 zero and/or x0 in [0,2^47), these return unreduced
1070 : results (in u44) with lanes 6 and 7 zero. */
1071 :
1072 : #define fd_r43x6_neg( x ) fd_r43x6_fold_signed ( fd_r43x6_neg_fast ( (x) ) )
1073 1323139 : #define fd_r43x6_add( x, y ) fd_r43x6_fold_unsigned( fd_r43x6_add_fast ( (x), (y) ) )
1074 601508 : #define fd_r43x6_sub( x, y ) fd_r43x6_fold_signed ( fd_r43x6_sub_fast ( (x), (y) ) )
1075 71186908 : #define fd_r43x6_mul( x, y ) fd_r43x6_fold_unsigned( fd_r43x6_mul_fast ( (x), (y) ) )
1076 764401862 : #define fd_r43x6_sqr( x ) fd_r43x6_fold_unsigned( fd_r43x6_sqr_fast ( (x) ) )
1077 : #define fd_r43x6_scale( x0, y ) fd_r43x6_fold_unsigned( fd_r43x6_scale_fast( (x0), (y) ) )
1078 :
1079 : /* fd_r43x6_invert(z) returns the multiplicative inverse of z in GF(p)
1080 : as an unreduced fd_r43x6_t (in u44) with lanes 6 and 7 zero where z
1081 : is an unreduced fd_r43x6_t (i.e. in u47) with lanes 6 and 7 zero. */
1082 :
1083 : FD_FN_CONST fd_r43x6_t
1084 : fd_r43x6_invert( fd_r43x6_t z );
1085 :
1086 : /* Miscellaneous APIs *************************************************/
1087 :
1088 : /* fd_r43x6_if(c,x,y) returns x if c is non-zero and y if not.
1089 : Branchless for deterministic timing. This macro is robust. */
1090 :
1091 2289740 : #define fd_r43x6_if(c,x,y) wwl_if( (-!(c)) & 0xff, (y), (x) )
1092 :
1093 : /* fd_r43x6_swap_if(c,x,y) will swap the contents of x and y if c is
1094 : non-zero and leave the contents of x and y unchanged otherwise.
1095 : Branchless for deterministic timing. This macro is robust. */
1096 :
1097 10000000 : #define fd_r43x6_swap_if(c,x,y) do { \
1098 10000000 : wwl_t _x = (x); \
1099 10000000 : wwl_t _y = (y); \
1100 10000000 : int _m = (-!(c)) & 0xff; \
1101 10000000 : (x) = wwl_if( _m, _x, _y ); \
1102 10000000 : (y) = wwl_if( _m, _y, _x ); \
1103 10000000 : } while(0)
1104 :
1105 : /* fd_r43x6_is_nonzero(x) reduces a signed fd_r43x6_t x (i.e. in s62)
1106 : and returns 0 if the result is zero and 1 if the result is non-zero. */
1107 :
1108 : FD_FN_UNUSED FD_FN_CONST static int /* Work around -Winline */
1109 1677410 : fd_r43x6_is_nonzero( fd_r43x6_t x ) {
1110 1677410 : long l0, l1, l2, l3, l4, l5;
1111 1677410 : fd_r43x6_extract_limbs( x, l ); /* l is signed */
1112 1677410 : fd_r43x6_biased_carry_propagate_limbs( l, l, 1L<<20 ); /* l is nearly reduced */
1113 1677410 : fd_r43x6_mod_nearly_reduced_limbs( l, l ); /* l is reduced */
1114 1677410 : return !!(l0|l1|l2|l3|l4|l5);
1115 1677410 : }
1116 :
1117 : /* fd_r43x6_diagnose(x) reduces a signed r43x6_t x (i.e. in s62) and
1118 : returns -1 if the result is zero and the least significant bit of the
1119 : reduced result otherwise. */
1120 :
1121 : FD_FN_UNUSED FD_FN_CONST static int /* Work around -Winline */
1122 519020 : fd_r43x6_diagnose( fd_r43x6_t x ) {
1123 519020 : long l0, l1, l2, l3, l4, l5;
1124 519020 : fd_r43x6_extract_limbs( x, l ); /* l is signed */
1125 519020 : fd_r43x6_biased_carry_propagate_limbs( l, l, 1L<<20 ); /* l is nearly reduced */
1126 519020 : fd_r43x6_mod_nearly_reduced_limbs( l, l ); /* l is reduced */
1127 519020 : return fd_int_if( !(l0|l1|l2|l3|l4|l5), -1, (int)(l0 & 1L) ); /* cmov */
1128 519020 : }
1129 :
1130 : /* fd_r43x6_pow22523(z) returns z^((p-5)/8) = z^(2^252 - 3) as an unreduced
1131 : fd_r43x6_t (in u44) with lanes 6 and 7 zero where z is an unreduced
1132 : r43x6_t (i.e. in u47) with lanes 6 and 7 of zero. */
1133 :
1134 : FD_FN_CONST fd_r43x6_t
1135 : fd_r43x6_pow22523( fd_r43x6_t z );
1136 :
1137 : FD_PROTOTYPES_END
1138 :
1139 : #include "fd_r43x6_inl.h"
1140 :
1141 : #endif /* FD_HAS_AVX512 */
1142 :
1143 : #endif /* HEADER_fd_src_ballet_ed25519_avx512_fd_r43x6_h */
|