@@ -6,15 +6,21 @@ use crate::{Alphabet, encode_base64_value};
66use core:: arch:: x86:: {
77 __m128i, __m256i, __m512i, _mm_add_epi8, _mm_and_si128, _mm_blendv_epi8, _mm_cmpeq_epi8,
88 _mm_cmpgt_epi8, _mm_loadu_si128, _mm_or_si128, _mm_set1_epi8, _mm_set1_epi32, _mm_setr_epi8,
9- _mm_shuffle_epi8, _mm_slli_epi32, _mm_srli_epi32, _mm_storeu_si128, _mm256_setzero_si256,
10- _mm256_storeu_si256, _mm512_setzero_si512, _mm512_storeu_si512,
9+ _mm_shuffle_epi8, _mm_slli_epi32, _mm_srli_epi32, _mm_storeu_si128, _mm256_add_epi8,
10+ _mm256_and_si256, _mm256_blendv_epi8, _mm256_cmpeq_epi8, _mm256_cmpgt_epi8, _mm256_loadu_si256,
11+ _mm256_or_si256, _mm256_set1_epi8, _mm256_set1_epi32, _mm256_setr_epi8, _mm256_shuffle_epi8,
12+ _mm256_slli_epi32, _mm256_srli_epi32, _mm256_storeu_si256, _mm512_setzero_si512,
13+ _mm512_storeu_si512,
1114} ;
1215#[ cfg( target_arch = "x86_64" ) ]
1316use core:: arch:: x86_64:: {
1417 __m128i, __m256i, __m512i, _mm_add_epi8, _mm_and_si128, _mm_blendv_epi8, _mm_cmpeq_epi8,
1518 _mm_cmpgt_epi8, _mm_loadu_si128, _mm_or_si128, _mm_set1_epi8, _mm_set1_epi32, _mm_setr_epi8,
16- _mm_shuffle_epi8, _mm_slli_epi32, _mm_srli_epi32, _mm_storeu_si128, _mm256_setzero_si256,
17- _mm256_storeu_si256, _mm512_setzero_si512, _mm512_storeu_si512,
19+ _mm_shuffle_epi8, _mm_slli_epi32, _mm_srli_epi32, _mm_storeu_si128, _mm256_add_epi8,
20+ _mm256_and_si256, _mm256_blendv_epi8, _mm256_cmpeq_epi8, _mm256_cmpgt_epi8, _mm256_loadu_si256,
21+ _mm256_or_si256, _mm256_set1_epi8, _mm256_set1_epi32, _mm256_setr_epi8, _mm256_shuffle_epi8,
22+ _mm256_slli_epi32, _mm256_srli_epi32, _mm256_storeu_si256, _mm512_setzero_si512,
23+ _mm512_storeu_si512,
1824} ;
1925
2026#[ allow( dead_code, reason = "inactive prototype is not dispatchable yet" ) ]
@@ -49,15 +55,44 @@ pub(crate) unsafe fn encode_24_bytes_avx2<A>(input: &[u8; 24], output: &mut [u8;
4955where
5056 A : Alphabet ,
5157{
52- let zeros = _mm256_setzero_si256 ( ) ;
53- // SAFETY: `output` is a valid 32-byte mutable array. AVX2 is guaranteed by
54- // this function's target feature precondition, and the unaligned store does
55- // not require any stronger pointer alignment.
56- unsafe {
57- _mm256_storeu_si256 ( output. as_mut_ptr ( ) . cast :: < __m256i > ( ) , zeros) ;
58+ if !is_standard_or_url_safe_family :: < A > ( ) {
59+ scalar_encode_block :: < A , 24 , 32 > ( input, output) ;
60+ return ;
5861 }
5962
60- scalar_encode_block :: < A , 24 , 32 > ( input, output) ;
63+ let mut staged = [
64+ input[ 0 ] , input[ 1 ] , input[ 2 ] , input[ 3 ] , input[ 4 ] , input[ 5 ] , input[ 6 ] , input[ 7 ] , input[ 8 ] ,
65+ input[ 9 ] , input[ 10 ] , input[ 11 ] , 0 , 0 , 0 , 0 , input[ 12 ] , input[ 13 ] , input[ 14 ] , input[ 15 ] ,
66+ input[ 16 ] , input[ 17 ] , input[ 18 ] , input[ 19 ] , input[ 20 ] , input[ 21 ] , input[ 22 ] , input[ 23 ] , 0 ,
67+ 0 , 0 , 0 ,
68+ ] ;
69+
70+ // SAFETY: `staged` and `output` are valid 32-byte arrays. The function's
71+ // target-feature contract enables AVX2. The load and store are unaligned
72+ // variants, and the shuffle mask uses `0x80` lanes only for zero-filled
73+ // bytes. The SIMD path is non-dispatchable test evidence.
74+ unsafe {
75+ let input_vec = _mm256_loadu_si256 ( staged. as_ptr ( ) . cast :: < __m256i > ( ) ) ;
76+ let shuffle = _mm256_setr_epi8 (
77+ 2 , 1 , 0 , -128 , 5 , 4 , 3 , -128 , 8 , 7 , 6 , -128 , 11 , 10 , 9 , -128 , 2 , 1 , 0 , -128 , 5 , 4 , 3 ,
78+ -128 , 8 , 7 , 6 , -128 , 11 , 10 , 9 , -128 ,
79+ ) ;
80+ let lanes = _mm256_shuffle_epi8 ( input_vec, shuffle) ;
81+
82+ let index0 = _mm256_and_si256 ( _mm256_srli_epi32 ( lanes, 18 ) , _mm256_set1_epi32 ( 0x0000_003f ) ) ;
83+ let index1 = _mm256_and_si256 ( _mm256_srli_epi32 ( lanes, 4 ) , _mm256_set1_epi32 ( 0x0000_3f00 ) ) ;
84+ let index2 = _mm256_and_si256 ( _mm256_slli_epi32 ( lanes, 10 ) , _mm256_set1_epi32 ( 0x003f_0000 ) ) ;
85+ let index3 = _mm256_and_si256 ( _mm256_slli_epi32 ( lanes, 24 ) , _mm256_set1_epi32 ( 0x3f00_0000 ) ) ;
86+ let indices = _mm256_or_si256 (
87+ _mm256_or_si256 ( index0, index1) ,
88+ _mm256_or_si256 ( index2, index3) ,
89+ ) ;
90+
91+ let encoded = encode_standard_family_indices_avx2 :: < A > ( indices) ;
92+ _mm256_storeu_si256 ( output. as_mut_ptr ( ) . cast :: < __m256i > ( ) , encoded) ;
93+ clear_ymm_registers_for_test_prototype ( ) ;
94+ }
95+ crate :: wipe_bytes ( & mut staged) ;
6196}
6297
6398#[ allow( dead_code, reason = "inactive prototype is not dispatchable yet" ) ]
@@ -166,6 +201,38 @@ where
166201 _mm_add_epi8 ( indices, offset)
167202}
168203
204+ #[ target_feature( enable = "avx2" ) ]
205+ unsafe fn encode_standard_family_indices_avx2 < A > ( indices : __m256i ) -> __m256i
206+ where
207+ A : Alphabet ,
208+ {
209+ let offset62 = if A :: ENCODE [ 62 ] == b'-' { -17 } else { -19 } ;
210+ let offset63 = if A :: ENCODE [ 63 ] == b'_' { 32 } else { -16 } ;
211+
212+ let lt26 = _mm256_cmpgt_epi8 ( _mm256_set1_epi8 ( 26 ) , indices) ;
213+ let lt52 = _mm256_cmpgt_epi8 ( _mm256_set1_epi8 ( 52 ) , indices) ;
214+ let lt62 = _mm256_cmpgt_epi8 ( _mm256_set1_epi8 ( 62 ) , indices) ;
215+ let eq62 = _mm256_cmpeq_epi8 ( _mm256_set1_epi8 ( 62 ) , indices) ;
216+
217+ let mut offset = _mm256_set1_epi8 ( offset63) ;
218+ offset = _mm256_blendv_epi8 ( offset, _mm256_set1_epi8 ( offset62) , eq62) ;
219+ offset = _mm256_blendv_epi8 ( offset, _mm256_set1_epi8 ( -4 ) , lt62) ;
220+ offset = _mm256_blendv_epi8 ( offset, _mm256_set1_epi8 ( 71 ) , lt52) ;
221+ offset = _mm256_blendv_epi8 ( offset, _mm256_set1_epi8 ( 65 ) , lt26) ;
222+
223+ _mm256_add_epi8 ( indices, offset)
224+ }
225+
226+ unsafe fn clear_ymm_registers_for_test_prototype ( ) {
227+ // SAFETY: The helper runs after the AVX2 prototype stores its output. The
228+ // XMM cleanup zeroes the lower halves declared to the compiler, and
229+ // `vzeroupper` clears upper YMM state before returning to scalar code.
230+ unsafe {
231+ clear_xmm_registers_for_test_prototype ( ) ;
232+ core:: arch:: asm!( "vzeroupper" , options( nostack, preserves_flags, nomem) ) ;
233+ }
234+ }
235+
169236#[ cfg( target_arch = "x86" ) ]
170237unsafe fn clear_xmm_registers_for_test_prototype ( ) {
171238 // SAFETY: This test-only cleanup runs after the prototype stores its
0 commit comments