1919// into a shift-or pair; selected on CPUs without AVX-512 (e.g.
2020// Raptor Lake desktop with both P- and E-cores active).
2121//
22- // Structure (same for both backends):
23- // Pass 1 — message schedule: w[40] linear array, sequential writes j=0..39.
24- // Keeping the linear array (rather than a circular buffer) lets the
25- // compiler hold w[j-1..j-8] in XMM registers across iterations.
26- // Pass 2 — compression: 8 rounds per outer step, W[t]+K[t] folded inline
27- // from w[t/2] + K[t/2] (one vector add per pair of rounds).
28- // No intermediate wk[80] array — eliminates 640 bytes of stack
29- // store-then-load traffic that was the main cost vs OpenSSL.
22+ // Both paths share the 8-way unrolled scalar compression rounds via the
23+ // DATAFORGE_SHA512_ROUND macro, which eliminates the 7 register-move
24+ // instructions that the naïve loop emits per round.
3025
3126namespace dataforge ::sha2_detail {
3227
@@ -94,51 +89,26 @@ __m128i sse41_sha512_sigma1(__m128i x)
9489}
9590
9691// ---------------------------------------------------------------------------
97- // Compression -round macro.
92+ // 8-way unrolled compression -round macro.
9893//
99- // Variables a..h are passed by name so each call- site permutes the argument
94+ // Variables a..h are passed by name so each call site permutes the argument
10095// list, rotating the logical state vector at compile time. No register moves
10196// are emitted between rounds — the compiler assigns each name to a fixed
10297// physical register for the whole 80-round sequence.
10398//
104- // wkval is the W[t]+K[t] scalar for this round, extracted from the XMM vector
105- // inline (no intermediate array store).
99+ // wk[] is the pre-computed W[t]+K[t] table; t is the round index (0..79).
106100// ---------------------------------------------------------------------------
107- #define DATAFORGE_SHA512_ROUND (a,b,c,d,e,f,g,h,wkval ) \
101+ #define DATAFORGE_SHA512_ROUND (a,b,c,d,e,f,g,h,wk,t ) \
108102do { \
109103 uint64_t _S1 = sha512_rotr (e,14 ) ^ sha512_rotr (e,18 ) ^ sha512_rotr (e,41 ); \
110- uint64_t _T1 = (h) + _S1 + ((e & f) ^ (~(e) & g)) + (uint64_t )(wkval); \
104+ uint64_t _T1 = (h) + _S1 + ((e & f) ^ (~(e) & g)) + (wk)[t]; \
111105 uint64_t _S0 = sha512_rotr (a,28 ) ^ sha512_rotr (a,34 ) ^ sha512_rotr (a,39 ); \
112106 (d) += _T1; \
113107 (h) = _T1 + _S0 + ((a & b) ^ (a & c) ^ (b & c)); \
114108} while (0 )
115109
116- // Two consecutive rounds from the low and high 64-bit lanes of one XMM vector.
117- // The 16 argument names encode the compile-time state-vector rotation.
118- // _mm_cvtsi128_si64 (SSE2) extracts low lane; _mm_extract_epi64 (SSE4.1) high.
119- #define DATAFORGE_SHA512_ROUNDS2 (a0,b0,c0,d0,e0 ,f0,g0,h0, \
120- a1,b1,c1,d1,e1 ,f1,g1,h1, vec) \
121- do { \
122- DATAFORGE_SHA512_ROUND (a0,b0,c0,d0,e0 ,f0,g0,h0, \
123- (uint64_t )_mm_cvtsi128_si64 (vec)); \
124- DATAFORGE_SHA512_ROUND (a1,b1,c1,d1,e1 ,f1,g1,h1, \
125- (uint64_t )_mm_extract_epi64 (vec, 1 )); \
126- } while (0 )
127-
128- // Eight rounds from four W+K vectors v0..v3. The round index entering this
129- // macro is always a multiple of 8 (both initial blocks and the expansion loop
130- // step by 4 vectors = 8 rounds), so the rotation always starts at (a,...,h)
131- // and returns to (a,...,h) after the four ROUNDS2 calls.
132- #define DATAFORGE_SHA512_8ROUNDS (v0, v1, v2, v3 ) \
133- do { \
134- DATAFORGE_SHA512_ROUNDS2 (a,b,c,d,e,f,g,h, h,a,b,c,d,e,f,g, v0); \
135- DATAFORGE_SHA512_ROUNDS2 (g,h,a,b,c,d,e,f, f,g,h,a,b,c,d,e, v1); \
136- DATAFORGE_SHA512_ROUNDS2 (e,f,g,h,a,b,c,d, d,e,f,g,h,a,b,c, v2); \
137- DATAFORGE_SHA512_ROUNDS2 (c,d,e,f,g,h,a,b, b,c,d,e,f,g,h,a, v3); \
138- } while (0 )
139-
140110// ---------------------------------------------------------------------------
141- // AVX-512VL backend: SIMD message schedule + inline-folded compression.
111+ // AVX-512VL backend: SIMD message schedule + 8-unrolled scalar compression
142112// ---------------------------------------------------------------------------
143113#if defined(_MSC_VER)
144114#pragma warning(push)
@@ -151,18 +121,17 @@ inline void process_blocks_sha512_x86_avx512(uint64_t(&state)[8], const void* ms
151121 static const __m128i SHUF_MASK = _mm_set_epi64x (0x08090A0B0C0D0E0FULL , 0x0001020304050607ULL );
152122
153123 const auto * data = reinterpret_cast <const uint8_t *>(msg);
154- const auto * K = reinterpret_cast <const __m128i*>(sha2_def_base<512 >::K);
124+ const auto * Kvec = reinterpret_cast <const __m128i*>(sha2_def_base<512 >::K);
155125
156- // Linear w[40]: two 64-bit words per XMM vector (W[2j], W[2j+1]).
157- // Linear layout lets the compiler keep w[j-1..j-8] in XMM registers
158- // across iterations of the schedule loop.
126+ // Message schedule: two 64-bit words per XMM vector (40 vectors = 80 words).
127+ // W[t] = sigma1(W[t-2]) + W[t-7] + sigma0(W[t-15]) + W[t-16]
128+ // W[t+1] = sigma1(W[t-1]) + W[t-6] + sigma0(W[t-14]) + W[t-15]
129+ // The sigma1 look-back is exactly the previous vector, so there is no
130+ // intra-vector dependency to break up.
159131 __m128i w[40 ];
160132 for (;;) {
161- // --- Pass 1: message schedule ---
162133 for (int i = 0 ; i < 8 ; ++i)
163- w[i] = _mm_shuffle_epi8 (
164- _mm_loadu_si128 (reinterpret_cast <const __m128i*>(data + 16 * i)),
165- SHUF_MASK );
134+ w[i] = _mm_shuffle_epi8 (_mm_loadu_si128 (reinterpret_cast <const __m128i*>(data + 16 * i)), SHUF_MASK );
166135
167136 for (int j = 8 ; j < 40 ; ++j) {
168137 const __m128i s0in = _mm_alignr_epi8 (w[j - 7 ], w[j - 8 ], 8 ); // {W[t-15], W[t-14]}
@@ -173,16 +142,27 @@ inline void process_blocks_sha512_x86_avx512(uint64_t(&state)[8], const void* ms
173142 w[j] = v;
174143 }
175144
176- // --- Pass 2: compression (W+K folded inline, no wk[] array) ---
145+ // Fold round constants in one pass (aligned loads from the 64-byte-aligned K
146+ // table) so the compression loop only needs a single load per round.
147+ alignas (64 ) uint64_t wk[80 ];
148+ for (int j = 0 ; j < 40 ; ++j)
149+ _mm_store_si128 (reinterpret_cast <__m128i*>(wk + 2 * j),
150+ _mm_add_epi64 (w[j], _mm_load_si128 (Kvec + j)));
151+
177152 uint64_t a = state[0 ], b = state[1 ], c = state[2 ], d = state[3 ];
178153 uint64_t e = state[4 ], f = state[5 ], g = state[6 ], h = state[7 ];
179154
180- for (int j = 0 ; j < 40 ; j += 4 ) {
181- __m128i v0 = _mm_add_epi64 (w[j ], K[j ]);
182- __m128i v1 = _mm_add_epi64 (w[j+1 ], K[j+1 ]);
183- __m128i v2 = _mm_add_epi64 (w[j+2 ], K[j+2 ]);
184- __m128i v3 = _mm_add_epi64 (w[j+3 ], K[j+3 ]);
185- DATAFORGE_SHA512_8ROUNDS (v0, v1, v2, v3);
155+ // 8-way unroll: argument permutation rotates the logical state vector,
156+ // eliminating all inter-round register moves.
157+ for (int t = 0 ; t < 80 ; t += 8 ) {
158+ DATAFORGE_SHA512_ROUND (a,b,c,d,e,f,g,h, wk, t+0 );
159+ DATAFORGE_SHA512_ROUND (h,a,b,c,d,e,f,g, wk, t+1 );
160+ DATAFORGE_SHA512_ROUND (g,h,a,b,c,d,e,f, wk, t+2 );
161+ DATAFORGE_SHA512_ROUND (f,g,h,a,b,c,d,e, wk, t+3 );
162+ DATAFORGE_SHA512_ROUND (e,f,g,h,a,b,c,d, wk, t+4 );
163+ DATAFORGE_SHA512_ROUND (d,e,f,g,h,a,b,c, wk, t+5 );
164+ DATAFORGE_SHA512_ROUND (c,d,e,f,g,h,a,b, wk, t+6 );
165+ DATAFORGE_SHA512_ROUND (b,c,d,e,f,g,h,a, wk, t+7 );
186166 }
187167
188168 state[0 ] += a; state[1 ] += b; state[2 ] += c; state[3 ] += d;
@@ -198,23 +178,22 @@ inline void process_blocks_sha512_x86_avx512(uint64_t(&state)[8], const void* ms
198178#endif
199179
200180// ---------------------------------------------------------------------------
201- // SSE4.1 backend: identical structure, sigma0/sigma1 use shift-or pairs
202- // instead of vprorq — runs on any x86-64 CPU since ~2008.
181+ // SSE4.1 backend: identical structure, but sigma0/sigma1 use shift-or pairs
182+ // instead of vprorq, so it runs on any x86-64 CPU (Raptor Lake desktop,
183+ // Alder Lake, Skylake, etc.) without requiring AVX-512.
203184// ---------------------------------------------------------------------------
204185DATAFORGE_SSE41_TARGET
205186inline void process_blocks_sha512_x86_sse41 (uint64_t (&state)[8], const void* msg, size_t block_count)
206187{
207188 static const __m128i SHUF_MASK = _mm_set_epi64x (0x08090A0B0C0D0E0FULL , 0x0001020304050607ULL );
208189
209190 const auto * data = reinterpret_cast <const uint8_t *>(msg);
210- const auto * K = reinterpret_cast <const __m128i*>(sha2_def_base<512 >::K);
191+ const auto * Kvec = reinterpret_cast <const __m128i*>(sha2_def_base<512 >::K);
211192
212193 __m128i w[40 ];
213194 for (;;) {
214195 for (int i = 0 ; i < 8 ; ++i)
215- w[i] = _mm_shuffle_epi8 (
216- _mm_loadu_si128 (reinterpret_cast <const __m128i*>(data + 16 * i)),
217- SHUF_MASK );
196+ w[i] = _mm_shuffle_epi8 (_mm_loadu_si128 (reinterpret_cast <const __m128i*>(data + 16 * i)), SHUF_MASK );
218197
219198 for (int j = 8 ; j < 40 ; ++j) {
220199 const __m128i s0in = _mm_alignr_epi8 (w[j - 7 ], w[j - 8 ], 8 );
@@ -225,15 +204,23 @@ inline void process_blocks_sha512_x86_sse41(uint64_t(&state)[8], const void* msg
225204 w[j] = v;
226205 }
227206
207+ alignas (64 ) uint64_t wk[80 ];
208+ for (int j = 0 ; j < 40 ; ++j)
209+ _mm_store_si128 (reinterpret_cast <__m128i*>(wk + 2 * j),
210+ _mm_add_epi64 (w[j], _mm_load_si128 (Kvec + j)));
211+
228212 uint64_t a = state[0 ], b = state[1 ], c = state[2 ], d = state[3 ];
229213 uint64_t e = state[4 ], f = state[5 ], g = state[6 ], h = state[7 ];
230214
231- for (int j = 0 ; j < 40 ; j += 4 ) {
232- __m128i v0 = _mm_add_epi64 (w[j ], K[j ]);
233- __m128i v1 = _mm_add_epi64 (w[j+1 ], K[j+1 ]);
234- __m128i v2 = _mm_add_epi64 (w[j+2 ], K[j+2 ]);
235- __m128i v3 = _mm_add_epi64 (w[j+3 ], K[j+3 ]);
236- DATAFORGE_SHA512_8ROUNDS (v0, v1, v2, v3);
215+ for (int t = 0 ; t < 80 ; t += 8 ) {
216+ DATAFORGE_SHA512_ROUND (a,b,c,d,e,f,g,h, wk, t+0 );
217+ DATAFORGE_SHA512_ROUND (h,a,b,c,d,e,f,g, wk, t+1 );
218+ DATAFORGE_SHA512_ROUND (g,h,a,b,c,d,e,f, wk, t+2 );
219+ DATAFORGE_SHA512_ROUND (f,g,h,a,b,c,d,e, wk, t+3 );
220+ DATAFORGE_SHA512_ROUND (e,f,g,h,a,b,c,d, wk, t+4 );
221+ DATAFORGE_SHA512_ROUND (d,e,f,g,h,a,b,c, wk, t+5 );
222+ DATAFORGE_SHA512_ROUND (c,d,e,f,g,h,a,b, wk, t+6 );
223+ DATAFORGE_SHA512_ROUND (b,c,d,e,f,g,h,a, wk, t+7 );
237224 }
238225
239226 state[0 ] += a; state[1 ] += b; state[2 ] += c; state[3 ] += d;
@@ -244,8 +231,6 @@ inline void process_blocks_sha512_x86_sse41(uint64_t(&state)[8], const void* msg
244231 }
245232}
246233
247- #undef DATAFORGE_SHA512_8ROUNDS
248- #undef DATAFORGE_SHA512_ROUNDS2
249234#undef DATAFORGE_SHA512_ROUND
250235
251236}
0 commit comments