irelia_encoder/
lib.rs

1#![cfg_attr(feature = "nightly", feature(array_chunks))]
2#![cfg_attr(feature = "simd", feature(portable_simd))]
3#![cfg_attr(all(feature = "nightly", test), feature(test))]
4#![warn(clippy::perf)]
5#![warn(clippy::pedantic)]
6#![no_std]
7
8//! This decoder is largely taking from this article. <https://dev.to/tiemen/implementing-base64-from-scratch-in-rust-kb1>
9//! It goes into detail about the entire thing, and why it works the way it does, and I highly recommend reading it
10//! Very big thanks to Tiemen for writing it!
11//!
12//! The usage of u64s as byte arrays is taken from the base64 crate, which is under the MIT license
13
14extern crate alloc;
15
16#[cfg(feature = "simd")]
17use core::{
18    mem::transmute_copy,
19    simd::{cmp::SimdPartialOrd, num::SimdUint, *},
20    slice,
21};
22
23use alloc::{string::String, vec};
24
25/// BASE64 encoder struct
26pub struct Encoder {
27    encode_table: [u8; 64],
28}
29
30impl Default for Encoder {
31    fn default() -> Self {
32        Self::new()
33    }
34}
35
36impl Encoder {
37    /// Creates a new instance of the encoder using the default base64 alphabet
38    ///
39    /// # Examples
40    /// ```
41    /// use irelia_encoder::Encoder;
42    ///
43    /// const ENCODER: Encoder = Encoder::new();
44    /// ```
45    #[must_use]
46    pub const fn new() -> Self {
47        Self {
48            encode_table: [
49                b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N',
50                b'O', b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', b'Z', b'a', b'b',
51                b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p',
52                b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', b'0', b'1', b'2', b'3',
53                b'4', b'5', b'6', b'7', b'8', b'9', b'+', b'/',
54            ],
55        }
56    }
57
58    #[cfg(not(feature = "simd"))]
59    /// Creates a new instance of the encoder using a specified alphabet
60    ///
61    /// # Example:
62    /// ```
63    /// use irelia_encoder::Encoder;
64    ///
65    /// const ALPHABET: [u8; 64] = [
66    ///     b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N',
67    ///     b'O', b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', b'Z', b'a', b'b',
68    ///     b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p',
69    ///     b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', b'0', b'1', b'2', b'3',
70    ///     b'4', b'5', b'6', b'7', b'8', b'9', b'+', b'/',
71    /// ];
72    ///
73    /// const ENCODER: Encoder = Encoder::with_encode_table(ALPHABET);
74    /// ```
75    #[must_use]
76    pub const fn with_encode_table(encode_table: [u8; 64]) -> Self {
77        Self { encode_table }
78    }
79
80    #[rustfmt::skip]
81    #[doc(hidden)]
82    /// Converts the buffer to base64, uses an out paramater to avoid allocations
83    pub fn internal_encode(&self, buf: &[u8], out: &mut [u8]) {
84        #[cfg(feature = "simd")]
85        let chunks = buf.array_chunks::<12>();
86        #[cfg(feature = "simd")]
87        let out_chunks = out.array_chunks_mut::<16>();
88
89        #[cfg(all(feature = "nightly", not(feature = "simd")))]
90        let chunks = buf.array_chunks::<24>();
91        #[cfg(all(feature = "nightly", not(feature = "simd")))]
92        let out_chunks = out.array_chunks_mut::<32>();
93
94        #[cfg(not(feature = "nightly"))]
95        let chunks = buf.chunks_exact(24);
96        #[cfg(not(feature = "nightly"))]
97        let out_chunks = out.chunks_exact_mut(32);
98
99        let mut output_index = 0;
100        let rem = chunks.remainder();
101
102        #[cfg(not(feature = "simd"))]
103        {
104            chunks.zip(out_chunks).for_each(|(chunk, out)| {
105                #[cfg(not(feature = "nightly"))]
106                let out: &mut [u8; 32] = out.try_into().unwrap();
107
108                let byte_array_1 = u64::from_be_bytes([
109                    0, 0, chunk[0], chunk[1], chunk[2], chunk[3], chunk[4], chunk[5],
110                ]);
111
112                let byte_array_2 = u64::from_be_bytes([
113                    0, 0, chunk[6], chunk[7], chunk[8], chunk[9], chunk[10], chunk[11],
114                ]);
115
116                let byte_array_3 = u64::from_be_bytes([
117                    0, 0, chunk[12], chunk[13], chunk[14], chunk[15], chunk[16], chunk[17],
118                ]);
119
120                let byte_array_4 = u64::from_be_bytes([
121                    0, 0, chunk[18], chunk[19], chunk[20], chunk[21], chunk[22], chunk[23],
122                ]);
123
124                *out = [
125                    self.encode_table[(byte_array_1 >> 42 & 0b0011_1111) as usize],
126                    self.encode_table[(byte_array_1 >> 36 & 0b0011_1111) as usize],
127                    self.encode_table[(byte_array_1 >> 30 & 0b0011_1111) as usize],
128                    self.encode_table[(byte_array_1 >> 24 & 0b0011_1111) as usize],
129                    self.encode_table[(byte_array_1 >> 18 & 0b0011_1111) as usize],
130                    self.encode_table[(byte_array_1 >> 12 & 0b0011_1111) as usize],
131                    self.encode_table[(byte_array_1 >> 6 & 0b0011_1111) as usize],
132                    self.encode_table[(byte_array_1 & 0b0011_1111) as usize],
133                    self.encode_table[(byte_array_2 >> 42 & 0b0011_1111) as usize],
134                    self.encode_table[(byte_array_2 >> 36 & 0b0011_1111) as usize],
135                    self.encode_table[(byte_array_2 >> 30 & 0b0011_1111) as usize],
136                    self.encode_table[(byte_array_2 >> 24 & 0b0011_1111) as usize],
137                    self.encode_table[(byte_array_2 >> 18 & 0b0011_1111) as usize],
138                    self.encode_table[(byte_array_2 >> 12 & 0b0011_1111) as usize],
139                    self.encode_table[(byte_array_2 >> 6 & 0b0011_1111) as usize],
140                    self.encode_table[(byte_array_2 & 0b0011_1111) as usize],
141                    self.encode_table[(byte_array_3 >> 42 & 0b0011_1111) as usize],
142                    self.encode_table[(byte_array_3 >> 36 & 0b0011_1111) as usize],
143                    self.encode_table[(byte_array_3 >> 30 & 0b0011_1111) as usize],
144                    self.encode_table[(byte_array_3 >> 24 & 0b0011_1111) as usize],
145                    self.encode_table[(byte_array_3 >> 18 & 0b0011_1111) as usize],
146                    self.encode_table[(byte_array_3 >> 12 & 0b0011_1111) as usize],
147                    self.encode_table[(byte_array_3 >> 6 & 0b0011_1111) as usize],
148                    self.encode_table[(byte_array_3 & 0b0011_1111) as usize],
149                    self.encode_table[(byte_array_4 >> 42 & 0b0011_1111) as usize],
150                    self.encode_table[(byte_array_4 >> 36 & 0b0011_1111) as usize],
151                    self.encode_table[(byte_array_4 >> 30 & 0b0011_1111) as usize],
152                    self.encode_table[(byte_array_4 >> 24 & 0b0011_1111) as usize],
153                    self.encode_table[(byte_array_4 >> 18 & 0b0011_1111) as usize],
154                    self.encode_table[(byte_array_4 >> 12 & 0b0011_1111) as usize],
155                    self.encode_table[(byte_array_4 >> 6 & 0b0011_1111) as usize],
156                    self.encode_table[(byte_array_4 & 0b0011_1111) as usize],
157                ];
158
159                output_index += 32;
160            });
161        }
162
163        #[cfg(feature = "simd")]
164        chunks.zip(out_chunks).for_each(|(chunk, out)| {
165            use crate::simd::*;
166
167            // # SAFETY: We ignore the last four values, which are the only ones that would be random
168            let buf = unsafe { slice::from_raw_parts(chunk.as_ptr(), 16) };
169            let vec: Simd<u8, 16> = Simd::from_slice(buf);
170
171            let indices = unpack_with_bswap(vec);
172
173            let chars = enc_translate(indices);
174
175            *out = chars.to_array();
176
177            output_index += 16;
178        });
179
180        let rem_out = &mut out[output_index..];
181
182        #[cfg(feature = "nightly")]
183        let chunks = rem.array_chunks::<3>();
184        #[cfg(feature = "nightly")]
185        let out_chunks = rem_out.array_chunks_mut::<4>();
186
187        #[cfg(not(feature = "nightly"))]
188        let chunks = rem.chunks_exact(3);
189        #[cfg(not(feature = "nightly"))]
190        let out_chunks = rem_out.chunks_exact_mut(4);
191
192        let rem = chunks.remainder();
193
194        chunks.zip(out_chunks).for_each(|(chunk, out)| {
195            #[cfg(not(feature = "nightly"))]
196            let out: &mut [u8; 4] = out.try_into().unwrap();
197
198            let byte_array = u32::from_be_bytes([chunk[0], chunk[1], chunk[2], 0]);
199            let bit_1 = byte_array >> 26 & 0b0011_1111;
200            let bit_2 = byte_array >> 20 & 0b0011_1111;
201            let bit_3 = byte_array >> 14 & 0b0011_1111;
202            let bit_4 = byte_array >> 8 & 0b0011_1111;
203
204            *out = [
205                self.encode_table[bit_1 as usize],
206                self.encode_table[bit_2 as usize],
207                self.encode_table[bit_3 as usize],
208                self.encode_table[bit_4 as usize],
209            ];
210
211            output_index += 4;
212        });
213
214        let rem_out = &mut out[output_index..];
215
216        #[cfg(feature = "nightly")]
217        let chunks = rem.array_chunks::<2>();
218        #[cfg(feature = "nightly")]
219        let out_chunks = rem_out.array_chunks_mut::<3>();
220
221        #[cfg(not(feature = "nightly"))]
222        let chunks = rem.chunks_exact(2);
223        #[cfg(not(feature = "nightly"))]
224        let out_chunks = rem_out.chunks_exact_mut(3);
225
226        let rem = chunks.remainder();
227
228        chunks.zip(out_chunks).for_each(|(chunk, out)| {
229            #[cfg(not(feature = "nightly"))]
230            let out: &mut [u8; 3] = out.try_into().unwrap();
231
232            let byte_array = u16::from_be_bytes([chunk[0], chunk[1]]);
233            let bit_1 = byte_array >> 10 & 0b0011_1111;
234            let bit_2 = byte_array >> 4 & 0b0011_1111;
235            let bit_3 = byte_array << 2 & 0b0011_1111;
236
237            *out = [
238                self.encode_table[bit_1 as usize],
239                self.encode_table[bit_2 as usize],
240                self.encode_table[bit_3 as usize],
241            ];
242
243            output_index += 3;
244        });
245
246
247        let rem_out = &mut out[output_index..];
248
249        #[cfg(feature = "nightly")]
250        let chunks = rem.array_chunks::<1>();
251        #[cfg(feature = "nightly")]
252        let out_chunks = rem_out.array_chunks_mut::<2>();
253
254        #[cfg(not(feature = "nightly"))]
255        let chunks = rem.chunks_exact(1);
256        #[cfg(not(feature = "nightly"))]
257        let out_chunks = rem_out.chunks_exact_mut(2);
258
259        chunks.zip(out_chunks).for_each(|(chunk, out)| {
260            #[cfg(not(feature = "nightly"))]
261            let out: &mut [u8; 2] = out.try_into().unwrap();
262
263            let byte = chunk[0];
264            let bit_1 = byte >> 2;
265            let bit_2 = (byte & 0b0000_0011) << 4;
266
267            *out = [
268                self.encode_table[bit_1 as usize],
269                self.encode_table[bit_2 as usize],
270            ];
271
272            output_index += 2;
273        });
274    }
275
276    /// Converts the bytes to BASE64
277    ///
278    /// # Examples
279    /// ```
280    /// use irelia_encoder::Encoder;
281    /// const ENCODER: Encoder = Encoder::new();
282    ///
283    /// let base64_encoded = ENCODER.encode("Hello, World!");
284    /// ```
285    ///
286    /// # Panics
287    /// This panics if the buffer does not produce valid utf-8,
288    /// this should never happen if the default alphabet is in use
289    pub fn encode<T>(&self, bytes: T) -> String
290    where
291        T: AsRef<[u8]>,
292    {
293        let buf = bytes.as_ref();
294        let mut out = vec![b'='; div_ceil(buf.len(), 3) * 4];
295        self.internal_encode(buf, &mut out);
296
297        String::from_utf8(out).unwrap()
298    }
299
300    /// Converts the bytes to BASE64, and validates that the BASE64 is all ASCII
301    ///
302    /// # Examples
303    /// ```
304    /// use irelia_encoder::Encoder;
305    /// const ENCODER: Encoder = Encoder::new();
306    ///
307    /// let base64_encoded = ENCODER.encode_with_ascii_check("Hello, World!");
308    /// ```
309    ///
310    /// # Panics
311    ///
312    /// This function panics if the buffer produced is not valid ASCII
313    /// This should not happen if the default alphabet is in use
314    pub fn encode_with_ascii_check<T>(&self, bytes: T) -> String
315    where
316        T: AsRef<[u8]>,
317    {
318        let buf = bytes.as_ref();
319        let mut out = vec![b'='; div_ceil(buf.len(), 3) * 4];
320        self.internal_encode(buf, &mut out);
321
322        assert!(is_ascii(&out));
323
324        // Safety: This is checked to be valid ASCII
325        // Which also makes it valid UTF-8
326        unsafe { String::from_utf8_unchecked(out) }
327    }
328
329    /// Converts the bytes to BASE64, but doesn't check if the output is valid UTF-8
330    ///
331    /// # Example:
332    /// ```
333    /// use irelia_encoder::Encoder;
334    /// const ENCODER: Encoder = Encoder::new();
335    ///
336    /// let base64_encoded = unsafe { ENCODER.encode_unchecked("Hello, World!") };
337    /// ```
338    ///
339    /// # Safety
340    ///
341    /// The characters used for the encode table need to be valid UTF-8
342    /// This is true with the default table, but might not be for custom ones.
343    ///
344    pub unsafe fn encode_unchecked<T>(&self, bytes: T) -> String
345    where
346        T: AsRef<[u8]>,
347    {
348        let buf = bytes.as_ref();
349        let mut out = vec![b'='; div_ceil(buf.len(), 3) * 4];
350        self.internal_encode(buf, &mut out);
351
352        String::from_utf8_unchecked(out)
353    }
354
355    /// Converts the bytes to BASE64 without padding
356    ///
357    /// # Examples
358    /// ```
359    /// use irelia_encoder::Encoder;
360    /// const ENCODER: Encoder = Encoder::new();
361    ///
362    /// let base64_encoded = ENCODER.encode_without_padding("Hello, World!");
363    /// ```
364    ///
365    /// # Panics
366    ///
367    /// This function will panic if the buffer does not produce valid UTF-8, which should never happen
368    pub fn encode_without_padding<T>(&self, bytes: T) -> String
369    where
370        T: AsRef<[u8]>,
371    {
372        let buf = bytes.as_ref();
373        let mut out = vec![0; div_ceil(buf.len(), 3) * 4];
374        self.internal_encode(buf, &mut out);
375
376        String::from_utf8(out).unwrap()
377    }
378
379    /// Converts the bytes to BASE64 without padding, but doesn't check if the output is valid UTF-8
380    ///
381    /// # Example:
382    /// ```
383    /// use irelia_encoder::Encoder;
384    /// const ENCODER: Encoder = Encoder::new();
385    ///
386    /// let base64_encoded = unsafe { ENCODER.encode_unchecked("Hello, World!") };
387    /// ```
388    ///
389    /// # Safety
390    ///
391    /// The characters used for the encode table need to be valid UTF-8
392    /// This is true with the default table, but might not be for custom ones.
393    ///
394    pub unsafe fn encode_unchecked_without_padding<T>(&self, bytes: T) -> String
395    where
396        T: AsRef<[u8]>,
397    {
398        let buf = bytes.as_ref();
399        let mut out = vec![0; div_ceil(buf.len(), 3) * 4];
400        self.internal_encode(buf, &mut out);
401
402        String::from_utf8_unchecked(out)
403    }
404}
405
406// Original Github: https://github.com/lemire/fastbase64/tree/master
407
408// Copyright (c) 2015-2016, Wojciech Muła, Alfred Klomp,  Daniel Lemire
409// (Unless otherwise stated in the source code)
410// All rights reserved.
411
412// Redistribution and use in source and binary forms, with or without
413// modification, are permitted provided that the following conditions are
414// met:
415
416// 1. Redistributions of source code must retain the above copyright
417//    notice, this list of conditions and the following disclaimer.
418
419// 2. Redistributions in binary form must reproduce the above copyright
420//    notice, this list of conditions and the following disclaimer in the
421//    documentation and/or other materials provided with the distribution.
422
423// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
424// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
425// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
426// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
427// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
428// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
429// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
430// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
431// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
432// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
433// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
434
435// Rust portable-simd port and additional optimizations
436// authored by AlsoSylv and burgerindividual
437#[cfg(feature = "simd")]
438mod simd {
439    use crate::swizzle_dyn;
440    use core::mem::transmute;
441    use core::simd::cmp::*;
442    use core::simd::num::*;
443    use core::simd::*;
444
445    pub fn unpack_with_bswap(input: u8x16) -> u8x16 {
446        let in_u8 = simd_swizzle!(input, [1, 0, 2, 1, 4, 3, 5, 4, 7, 6, 8, 7, 10, 9, 11, 10,]);
447
448        // # SAFETY: Transmute is only used to convert between same sized simd types
449        unsafe {
450            let in_u32: u32x4 = transmute(in_u8);
451
452            let t0 = in_u32 & u32x4::splat(0x0fc0fc00);
453            let t0_u16 = transmute::<_, u16x8>(t0);
454
455            let t1 = simd_swizzle!(
456                t0_u16 >> Simd::splat(10),
457                t0_u16 >> Simd::splat(6),
458                [
459                    0,  // First(0),
460                    9,  // Second(1),
461                    2,  // First(2),
462                    11, // Second(3),
463                    4,  // First(4),
464                    13, // Second(5),
465                    6,  // First(6),
466                    15, // Second(7),
467                ]
468            );
469
470            let t2 = in_u32 & u32x4::splat(0x003f03f0);
471
472            let t3 = transmute::<_, u16x8>(t2)
473                * u16x8::from_array([
474                    0x0010, 0x0100, 0x0010, 0x0100, 0x0010, 0x0100, 0x0010, 0x0100,
475                ]);
476
477            transmute(t1 | t3)
478        }
479    }
480
481    pub(crate) fn enc_translate(input: u8x16) -> u8x16 {
482        let lut: Simd<i8, 16> = Simd::from_array([
483            65, 71, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -19, -16, 0, 0,
484        ]);
485        let indices = input.saturating_sub(Simd::splat(51));
486        let mask = input.simd_gt(Simd::splat(25)).to_int().cast::<u8>();
487
488        let indices = indices - mask;
489
490        let out = input.cast::<i8>() + swizzle_dyn(lut.cast::<u8>(), indices).cast::<i8>();
491
492        out.cast::<u8>()
493    }
494}
495
496#[cfg(feature = "simd")]
497fn is_ascii(buffer: &[u8]) -> bool {
498    let mut mask: Simd<i8, 16> = Simd::splat(0);
499    let spans = buffer.array_chunks::<256>();
500    let rem = spans.remainder();
501
502    let mut has_error: Mask<i8, 16>;
503
504    for span in spans {
505        let chunks = span.array_chunks::<16>();
506
507        for chunk in chunks {
508            let current_bytes = Simd::from_slice(chunk);
509            mask |= current_bytes.cast();
510        }
511
512        has_error = mask.simd_lt(Simd::splat(0));
513
514        if has_error.any() {
515            return false;
516        }
517    }
518
519    let chunks = rem.array_chunks::<16>();
520    let rem = chunks.remainder();
521
522    for chunk in chunks {
523        let current_bytes = Simd::from_slice(chunk);
524        mask |= current_bytes.cast();
525    }
526
527    has_error = mask.simd_lt(Simd::splat(0));
528
529    if has_error.any() {
530        return false;
531    }
532
533    for x in rem {
534        if x > &127 {
535            return false;
536        }
537    }
538
539    true
540}
541
542#[cfg(not(feature = "simd"))]
543fn is_ascii(buffer: &[u8]) -> bool {
544    #[cfg(feature = "nightly")]
545    let spans = buffer.array_chunks::<256>();
546    #[cfg(not(feature = "nightly"))]
547    let spans = buffer.chunks_exact(256);
548    let rem = spans.remainder();
549    for span in spans {
550        if span > &[127; 256] {
551            return false;
552        }
553    }
554
555    for x in rem {
556        if x > &127 {
557            return false;
558        }
559    }
560
561    true
562}
563
564#[inline]
565const fn div_ceil(divisor: usize, dividend: usize) -> usize {
566    let quotient = divisor / dividend;
567    let remainder = divisor % dividend;
568
569    if remainder > 0 {
570        quotient + 1
571    } else {
572        quotient
573    }
574}
575
576/// Swizzle a vector of bytes according to the index vector.
577/// Indices within range select the appropriate byte.
578/// Indices "out of bounds" instead select 0.
579///
580/// Note that the current implementation is selected during build-time
581/// of the standard library, so `cargo build -Zbuild-std` may be necessary
582/// to unlock better performance, especially for larger vectors.
583/// A planned compiler improvement will enable using `#[target_feature]` instead.
584#[cfg(feature = "simd")]
585#[inline]
586fn swizzle_dyn<const N: usize>(val: Simd<u8, N>, idxs: Simd<u8, N>) -> Simd<u8, N>
587where
588    LaneCount<N>: SupportedLaneCount,
589{
590    #![allow(unused_imports, unused_unsafe)]
591    #[cfg(all(target_arch = "aarch64", target_endian = "little"))]
592    use core::arch::aarch64::{uint8x8_t, vqtbl1q_u8, vtbl1_u8};
593    #[cfg(all(target_arch = "arm", target_feature = "v7", target_endian = "little"))]
594    use core::arch::arm::{uint8x8_t, vtbl1_u8};
595    #[cfg(target_arch = "wasm32")]
596    use core::arch::wasm32 as wasm;
597    #[cfg(target_arch = "x86")]
598    use core::arch::x86;
599    #[cfg(target_arch = "x86_64")]
600    use core::arch::x86_64 as x86;
601    // SAFETY: Intrinsics covered by cfg
602    unsafe {
603        #[cfg(target_feature = "ssse3")]
604        return transize(x86::_mm_shuffle_epi8, val, idxs);
605        #[cfg(target_feature = "simd128")]
606        return transize(wasm::i8x16_swizzle, val, idxs);
607        #[cfg(all(
608            target_arch = "aarch64",
609            target_feature = "neon",
610            target_endian = "little"
611        ))]
612        return transize(vqtbl1q_u8, val, idxs);
613    }
614}
615
616/// This sets up a call to an architecture-specific function, and in doing so
617/// it persuades rustc that everything is the correct size. Which it is.
618/// This would not be needed if one could convince Rust that, by matching on N,
619/// N is that value, and thus it would be valid to substitute e.g. 16.
620///
621/// # Safety
622/// The correctness of this function hinges on the sizes agreeing in actuality.
623#[cfg(feature = "simd")]
624#[allow(dead_code)]
625#[inline(always)]
626unsafe fn transize<T, const N: usize>(
627    f: unsafe fn(T, T) -> T,
628    bytes: Simd<u8, N>,
629    idxs: Simd<u8, N>,
630) -> Simd<u8, N>
631where
632    LaneCount<N>: SupportedLaneCount,
633{
634    let idxs = zeroing_idxs(idxs);
635    // SAFETY: Same obligation to use this function as to use transmute_copy.
636    unsafe { transmute_copy(&f(transmute_copy(&bytes), transmute_copy(&idxs))) }
637}
638
639/// Make indices that yield 0 for this architecture
640#[cfg(feature = "simd")]
641#[inline(always)]
642fn zeroing_idxs<const N: usize>(idxs: Simd<u8, N>) -> Simd<u8, N>
643where
644    LaneCount<N>: SupportedLaneCount,
645{
646    // On x86, make sure the top bit is set.
647    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
648    let idxs = {
649        idxs.simd_lt(Simd::splat(N as u8))
650            .select(idxs, Simd::splat(u8::MAX))
651    };
652    // Simply do nothing on most architectures.
653    idxs
654}
655
656#[cfg(all(test, not(feature = "nightly")))]
657#[test]
658fn b64_validity_check() {
659    use base64::{engine::general_purpose, Engine};
660    use rand::distributions::{Alphanumeric, DistString};
661    use rand::thread_rng;
662
663    for _ in 0..100_000 {
664        let mut rng = thread_rng();
665        let string = Alphanumeric.sample_string(&mut rng, 34);
666        let b64_encoded = general_purpose::STANDARD.encode(string.clone());
667        let encoder = Encoder::new();
668        let my_encoded = encoder.encode(string);
669
670        assert_eq!(my_encoded, b64_encoded);
671    }
672}
673
674#[cfg(all(test, feature = "nightly"))]
675/*
676These are the current benchmark results running on a Ryzen 9 7900x
677Note: This was after recent changes to core::simd
678
679Simd Feature Enabled:
680base64_crate               ... bench:   7,008,375 ns/iter (+/- 660,934)
681irelia_encoder             ... bench:   2,859,420 ns/iter (+/- 708,613)
682irelia_encoder_ascii_check ... bench:   2,509,612 ns/iter (+/- 378,540)
683irelia_encoder_unchecked   ... bench:   2,260,470 ns/iter (+/- 609,514)
684
685Nightly Feature Enabled:
686base64_crate               ... bench:   6,892,705 ns/iter (+/- 413,544)
687irelia_encoder             ... bench:   5,896,720 ns/iter (+/- 483,676)
688irelia_encoder_ascii_check ... bench:   5,613,385 ns/iter (+/- 478,652)
689irelia_encoder_unchecked   ... bench:   5,432,165 ns/iter (+/- 379,335)
690
691These are the current benchmark results running on a Ryzen 7 5700u
692Note: This was before recent changes to core::simd
693
694Simd Feature Enabled:
695base64_crate               ... bench: 8,935,681 ns/iter (+/- 289,240)
696irelia_encoder             ... bench: 4,270,517 ns/iter (+/- 61,791)
697irelia_encoder_ascii_check ... bench: 3,595,404 ns/iter (+/- 79,627)
698irelia_encoder_unchecked   ... bench: 3,306,281 ns/iter (+/- 39,131)
699
700Nightly Feature Enabled:
701base64_crate               ... bench: 8,896,750 ns/iter (+/- 148,658)
702irelia_encoder             ... bench: 7,312,479 ns/iter (+/- 24,627)
703irelia_encoder_ascii_check ... bench: 6,871,569 ns/iter (+/- 18,158)
704irelia_encoder_unchecked   ... bench: 6,589,772 ns/iter (+/- 55,076)
705
706Stable Toolchain:
707base64_crate               ... bench: 9,042,472 ns/iter (+/- 261,678)
708irelia_encoder             ... bench: 7,382,325 ns/iter (+/- 87,712)
709irelia_encoder_ascii_check ... bench: 6,951,625 ns/iter (+/- 21,298)
710irelia_encoder_unchecked   ... bench: 6,578,281 ns/iter (+/- 21,827)
711
712Stable Toolchain no rustflags:
713base64_crate               ... bench: 8,532,239 ns/iter (+/- 31,178)
714irelia_encoder             ... bench: 8,069,776 ns/iter (+/- 95,354)
715irelia_encoder_ascii_check ... bench: 7,568,010 ns/iter (+/- 23,848)
716irelia_encoder_unchecked   ... bench: 7,190,625 ns/iter (+/- 58,626)
717*/
718mod test {
719    extern crate test;
720
721    use alloc::{string::String, vec};
722    use test::{black_box, Bencher};
723
724    use base64::{engine::general_purpose, Engine};
725    use rand::{
726        distributions::{Alphanumeric, DistString},
727        thread_rng,
728    };
729
730    use super::Encoder;
731
732    #[test]
733    fn b64_validity_check() {
734        for _ in 0..100000 {
735            let mut rng = thread_rng();
736            let string = Alphanumeric.sample_string(&mut rng, 34);
737            let b64_encoded = general_purpose::STANDARD.encode(string.clone());
738            let encoder = Encoder::new();
739            let my_encoded = encoder.encode(string);
740
741            if !my_encoded.eq(&b64_encoded) {
742                panic!("{my_encoded:?} != {b64_encoded:?}")
743            }
744        }
745    }
746
747    #[bench]
748    fn irelia_encoder(b: &mut Bencher) {
749        let mut strings = vec![String::new(); 10000];
750
751        let mut rng = black_box(thread_rng());
752
753        for x in 0..10000 {
754            strings[x] = black_box(Alphanumeric.sample_string(&mut rng, 1924));
755        }
756
757        let encoder = Encoder::new();
758
759        b.iter(|| {
760            black_box(for x in 0..10000 {
761                black_box(encoder.encode(&strings[x]));
762            });
763        })
764    }
765
766    #[bench]
767    fn irelia_encoder_ascii_check(b: &mut Bencher) {
768        let mut strings = vec![String::new(); 10000];
769
770        let mut rng = black_box(thread_rng());
771
772        for x in 0..10000 {
773            strings[x] = black_box(Alphanumeric.sample_string(&mut rng, 1924));
774        }
775
776        let encoder = Encoder::new();
777
778        b.iter(|| {
779            black_box({
780                for x in 0..10000 {
781                    black_box(encoder.encode_with_ascii_check(&strings[x]));
782                }
783            });
784        })
785    }
786
787    #[bench]
788    fn irelia_encoder_unchecked(b: &mut Bencher) {
789        let mut strings = vec![String::new(); 10000];
790
791        let mut rng = black_box(thread_rng());
792
793        for x in 0..10000 {
794            strings[x] = black_box(Alphanumeric.sample_string(&mut rng, 1924));
795        }
796
797        let encoder = Encoder::new();
798
799        b.iter(|| {
800            black_box({
801                for x in 0..10000 {
802                    black_box(unsafe { encoder.encode_unchecked(&strings[x]) });
803                }
804            });
805        })
806    }
807
808    #[bench]
809    fn base64_crate(b: &mut Bencher) {
810        let mut strings = vec![String::new(); 10000];
811
812        let mut rng = black_box(thread_rng());
813
814        for x in 0..10000 {
815            strings[x] = black_box(Alphanumeric.sample_string(&mut rng, 1924));
816        }
817
818        b.iter(|| {
819            black_box({
820                for x in 0..10000 {
821                    general_purpose::STANDARD.encode(&strings[x]);
822                }
823            });
824        })
825    }
826}