1use core::sync::atomic::{AtomicU8, Ordering};
25
26#[cfg(target_arch = "aarch64")]
27mod neon;
28#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
29mod x86;
30#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
31mod wasm128;
32
33#[derive(Copy, Clone, Debug, PartialEq, Eq)]
35#[repr(u8)]
36pub enum Base64Backend {
37 Scalar = 0,
39 Neon = 1,
41 Ssse3 = 2,
43 Avx2 = 3,
46 Wasm128 = 4,
48}
49
50impl Base64Backend {
51 #[cfg(target_arch = "aarch64")]
53 pub const ALL: &'static [Base64Backend] =
54 &[Base64Backend::Scalar, Base64Backend::Neon];
55 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
57 pub const ALL: &'static [Base64Backend] = &[
58 Base64Backend::Scalar,
59 Base64Backend::Ssse3,
60 Base64Backend::Avx2,
61 ];
62 #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
64 pub const ALL: &'static [Base64Backend] =
65 &[Base64Backend::Scalar, Base64Backend::Wasm128];
66 #[cfg(not(any(
68 target_arch = "aarch64",
69 target_arch = "x86",
70 target_arch = "x86_64",
71 all(target_arch = "wasm32", target_feature = "simd128")
72 )))]
73 pub const ALL: &'static [Base64Backend] = &[Base64Backend::Scalar];
74
75 #[must_use]
77 pub const fn name(self) -> &'static str {
78 match self {
79 Base64Backend::Scalar => "scalar",
80 Base64Backend::Neon => "neon",
81 Base64Backend::Ssse3 => "ssse3",
82 Base64Backend::Avx2 => "avx2",
83 Base64Backend::Wasm128 => "wasm128",
84 }
85 }
86
87 #[must_use]
89 pub fn is_available(self) -> bool {
90 match self {
91 Base64Backend::Scalar => true,
92 Base64Backend::Neon => have_neon(),
93 Base64Backend::Ssse3 => have_ssse3(),
94 Base64Backend::Avx2 => have_avx2() && have_ssse3(),
97 Base64Backend::Wasm128 => have_wasm_simd128(),
98 }
99 }
100
101 #[inline]
102 const fn from_u8(value: u8) -> Base64Backend {
103 match value {
104 1 => Base64Backend::Neon,
105 2 => Base64Backend::Ssse3,
106 3 => Base64Backend::Avx2,
107 4 => Base64Backend::Wasm128,
108 _ => Base64Backend::Scalar,
109 }
110 }
111}
112
113impl core::fmt::Display for Base64Backend {
114 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
115 f.write_str(self.name())
116 }
117}
118
119#[cfg(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64")))]
120#[inline]
121fn have_ssse3() -> bool {
122 std::arch::is_x86_feature_detected!("ssse3")
123}
124#[cfg(not(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64"))))]
125#[inline]
126fn have_ssse3() -> bool {
127 cfg!(all(
128 any(target_arch = "x86", target_arch = "x86_64"),
129 target_feature = "ssse3"
130 ))
131}
132
133#[cfg(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64")))]
134#[inline]
135fn have_avx2() -> bool {
136 std::arch::is_x86_feature_detected!("avx2")
137}
138#[cfg(not(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64"))))]
139#[inline]
140fn have_avx2() -> bool {
141 cfg!(all(
142 any(target_arch = "x86", target_arch = "x86_64"),
143 target_feature = "avx2"
144 ))
145}
146
147#[cfg(all(feature = "std", target_arch = "aarch64"))]
148#[inline]
149fn have_neon() -> bool {
150 #[cfg(any(target_vendor = "apple", target_os = "windows"))]
151 {
152 true
153 }
154 #[cfg(not(any(target_vendor = "apple", target_os = "windows")))]
155 {
156 std::arch::is_aarch64_feature_detected!("neon")
157 }
158}
159
160#[inline]
161fn have_wasm_simd128() -> bool {
162 cfg!(all(target_arch = "wasm32", target_feature = "simd128"))
163}
164#[cfg(not(all(feature = "std", target_arch = "aarch64")))]
165#[inline]
166fn have_neon() -> bool {
167 cfg!(all(target_arch = "aarch64", target_feature = "neon"))
168}
169
170const UNINIT: u8 = u8::MAX;
171static CACHED_BACKEND: AtomicU8 = AtomicU8::new(UNINIT);
172
173#[must_use]
175pub fn detect_base64_backend() -> Base64Backend {
176 if cfg!(miri) {
177 Base64Backend::Scalar
178 } else if have_avx2() && have_ssse3() {
179 Base64Backend::Avx2
180 } else if have_ssse3() {
181 Base64Backend::Ssse3
182 } else if have_neon() {
183 Base64Backend::Neon
184 } else if have_wasm_simd128() {
185 Base64Backend::Wasm128
186 } else {
187 Base64Backend::Scalar
188 }
189}
190
191#[cold]
192#[inline(never)]
193fn detect_and_cache() -> Base64Backend {
194 let detected = detect_base64_backend();
195 CACHED_BACKEND.store(detected as u8, Ordering::Relaxed);
196 detected
197}
198
199#[inline]
201#[must_use]
202pub fn base64_backend() -> Base64Backend {
203 let cached = CACHED_BACKEND.load(Ordering::Relaxed);
204 if cached == UNINIT {
205 detect_and_cache()
206 } else {
207 Base64Backend::from_u8(cached)
208 }
209}
210
211#[cfg(target_arch = "aarch64")]
214pub const MIN_ENCODE_LEN: usize = 24;
215#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
217pub const MIN_ENCODE_LEN: usize = 16;
218#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
221pub const MIN_ENCODE_LEN: usize = 16;
222#[cfg(not(any(
224 target_arch = "aarch64",
225 target_arch = "x86",
226 target_arch = "x86_64",
227 all(target_arch = "wasm32", target_feature = "simd128")
228)))]
229pub const MIN_ENCODE_LEN: usize = usize::MAX;
230
231#[cfg(target_arch = "aarch64")]
233pub const MIN_DECODE_LEN: usize = 32;
234#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
236pub const MIN_DECODE_LEN: usize = 16;
237#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
239pub const MIN_DECODE_LEN: usize = 16;
240#[cfg(not(any(
242 target_arch = "aarch64",
243 target_arch = "x86",
244 target_arch = "x86_64",
245 all(target_arch = "wasm32", target_feature = "simd128")
246)))]
247pub const MIN_DECODE_LEN: usize = usize::MAX;
248
249#[inline]
258pub unsafe fn encode_prefix(
259 backend: Base64Backend,
260 dst: *mut u8,
261 src: &[u8],
262) -> (usize, usize) {
263 #[cfg(not(any(
264 target_arch = "aarch64",
265 target_arch = "x86",
266 target_arch = "x86_64",
267 all(target_arch = "wasm32", target_feature = "simd128")
268 )))]
269 let _ = (dst, src);
270
271 match backend {
272 Base64Backend::Scalar => (0, 0),
273
274 #[cfg(target_arch = "aarch64")]
275 Base64Backend::Neon => unsafe { neon::encode(dst, src.as_ptr(), src.len()) },
278 #[cfg(not(target_arch = "aarch64"))]
279 Base64Backend::Neon => (0, 0),
280
281 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
282 Base64Backend::Ssse3 => unsafe { x86::encode_ssse3(dst, src.as_ptr(), src.len()) },
285 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
286 Base64Backend::Ssse3 => (0, 0),
287
288 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
289 Base64Backend::Avx2 => unsafe { x86::encode_avx2(dst, src.as_ptr(), src.len()) },
291 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
292 Base64Backend::Avx2 => (0, 0),
293
294 #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
295 Base64Backend::Wasm128 => unsafe { wasm128::encode(dst, src.as_ptr(), src.len()) },
298 #[cfg(not(all(target_arch = "wasm32", target_feature = "simd128")))]
299 Base64Backend::Wasm128 => (0, 0),
300 }
301}
302
303#[inline]
312pub unsafe fn decode_prefix(
313 backend: Base64Backend,
314 dst: *mut u8,
315 dst_len: usize,
316 src: &[u8],
317) -> (usize, usize) {
318 #[cfg(not(any(
319 target_arch = "aarch64",
320 target_arch = "x86",
321 target_arch = "x86_64",
322 all(target_arch = "wasm32", target_feature = "simd128")
323 )))]
324 let _ = (dst, dst_len, src);
325
326 match backend {
327 Base64Backend::Scalar => (0, 0),
328
329 #[cfg(target_arch = "aarch64")]
330 Base64Backend::Neon => unsafe { neon::decode(dst, dst_len, src.as_ptr(), src.len()) },
333 #[cfg(not(target_arch = "aarch64"))]
334 Base64Backend::Neon => (0, 0),
335
336 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
337 Base64Backend::Ssse3 => unsafe { x86::decode_ssse3(dst, dst_len, src.as_ptr(), src.len()) },
340 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
341 Base64Backend::Ssse3 => (0, 0),
342
343 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
344 Base64Backend::Avx2 => unsafe { x86::decode_avx2(dst, dst_len, src.as_ptr(), src.len()) },
346 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
347 Base64Backend::Avx2 => (0, 0),
348
349 #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
350 Base64Backend::Wasm128 => unsafe {
353 wasm128::decode(dst, dst_len, src.as_ptr(), src.len())
354 },
355 #[cfg(not(all(target_arch = "wasm32", target_feature = "simd128")))]
356 Base64Backend::Wasm128 => (0, 0),
357 }
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363
364 #[test]
365 fn detection_is_cached_and_executable() {
366 let detected = detect_base64_backend();
367 assert!(detected.is_available());
368 assert_eq!(base64_backend(), detected);
369 assert_eq!(base64_backend(), detected);
370 }
371
372 #[test]
373 fn backend_discriminants_round_trip() {
374 for &backend in Base64Backend::ALL {
375 assert_eq!(Base64Backend::from_u8(backend as u8), backend);
376 }
377 assert_eq!(Base64Backend::from_u8(UNINIT), Base64Backend::Scalar);
378 assert_eq!(Base64Backend::from_u8(200), Base64Backend::Scalar);
379 }
380}