Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions src/arch/aarch64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use super::generic;
use crate::{get_chars_table, Output};
use core::arch::aarch64::*;
use core::mem::MaybeUninit;

pub(crate) const USE_CHECK_FN: bool = false;

Expand Down Expand Up @@ -85,15 +86,15 @@ pub(crate) unsafe fn check_neon(input: &[u8]) -> bool {
///
/// Based on: <http://0x80.pl/notesen/2022-01-17-validating-hex-parse.html>
#[inline]
pub(crate) unsafe fn decode_checked(input: &[u8], output: &mut [u8]) -> bool {
pub(crate) unsafe fn decode_checked(input: &[u8], output: &mut [MaybeUninit<u8>]) -> bool {
if cfg!(miri) || !has_neon() {
return generic::decode_checked(input, output);
}
decode_checked_neon(input, output)
}

#[target_feature(enable = "neon")]
unsafe fn decode_checked_neon(input: &[u8], output: &mut [u8]) -> bool {
unsafe fn decode_checked_neon(input: &[u8], output: &mut [MaybeUninit<u8>]) -> bool {
debug_assert_eq!(output.len(), input.len() / 2);

let add_c6 = vdupq_n_u8(0xC6); // 0xFF - b'9'
Expand Down Expand Up @@ -130,15 +131,15 @@ unsafe fn decode_checked_neon(input: &[u8], output: &mut [u8]) -> bool {
}

#[inline]
pub(crate) unsafe fn decode_unchecked(input: &[u8], output: &mut [u8]) {
pub(crate) unsafe fn decode_unchecked(input: &[u8], output: &mut [MaybeUninit<u8>]) {
if cfg!(miri) || !has_neon() {
return generic::decode_unchecked(input, output);
}
decode_unchecked_neon(input, output);
}

#[target_feature(enable = "neon")]
unsafe fn decode_unchecked_neon(input: &[u8], output: &mut [u8]) {
unsafe fn decode_unchecked_neon(input: &[u8], output: &mut [MaybeUninit<u8>]) {
generic::decode_unchecked_unaligned_chunks(input, output, |[v0, v1]: [uint8x16_t; 2]| {
let n0 = unhex_neon(v0);
let n1 = unhex_neon(v1);
Expand Down
4 changes: 2 additions & 2 deletions src/arch/generic.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{byte2hex, Output, HEX_DECODE_LUT, NIL};
use core::mem::size_of;
use core::mem::{size_of, MaybeUninit};

/// Set to `true` to use `check` + `decode_unchecked` for decoding. Otherwise uses `decode_checked`.
///
Expand Down Expand Up @@ -143,7 +143,7 @@ pub(crate) fn check_one_unaligned_chunk<T: Copy>(
///
/// Assumes `output.len() == input.len() / 2`.
#[allow(dead_code)]
pub(crate) unsafe fn decode_checked(input: &[u8], output: &mut [u8]) -> bool {
pub(crate) unsafe fn decode_checked(input: &[u8], output: &mut [MaybeUninit<u8>]) -> bool {
unsafe { decode_maybe_check::<true>(input, output) }
}

Expand Down
5 changes: 3 additions & 2 deletions src/arch/portable_simd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use super::generic;
use crate::{get_chars_table, Output};
use core::mem::MaybeUninit;
use core::simd::prelude::*;

type Simd = u8x16;
Expand Down Expand Up @@ -45,7 +46,7 @@ pub(crate) fn check(input: &[u8]) -> bool {
/// - Nibble pairs are merged with `deinterleave` + `(hi << 4) | lo`.
///
/// Based on: <http://0x80.pl/notesen/2022-01-17-validating-hex-parse.html>
pub(crate) unsafe fn decode_checked(input: &[u8], output: &mut [u8]) -> bool {
pub(crate) unsafe fn decode_checked(input: &[u8], output: &mut [MaybeUninit<u8>]) -> bool {
debug_assert_eq!(output.len(), input.len() / 2);

let add_c6 = Simd::splat(0xC6); // 0xFF - b'9'
Expand Down Expand Up @@ -81,7 +82,7 @@ pub(crate) unsafe fn decode_checked(input: &[u8], output: &mut [u8]) -> bool {
})
}

pub(crate) unsafe fn decode_unchecked(input: &[u8], output: &mut [u8]) {
pub(crate) unsafe fn decode_unchecked(input: &[u8], output: &mut [MaybeUninit<u8>]) {
generic::decode_unchecked_unaligned_chunks(input, output, |[v0, v1]: [Simd; 2]| {
let n0 = unhex(v0);
let n1 = unhex(v1);
Expand Down
5 changes: 3 additions & 2 deletions src/arch/wasm32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use super::generic;
use crate::{get_chars_table, Output};
use core::arch::wasm32::*;
use core::mem::MaybeUninit;

pub(crate) const USE_CHECK_FN: bool = false;

Expand Down Expand Up @@ -80,7 +81,7 @@ pub(crate) fn check(input: &[u8]) -> bool {
/// Based on: <http://0x80.pl/notesen/2022-01-17-validating-hex-parse.html>
#[inline]
#[target_feature(enable = "simd128")]
pub(crate) unsafe fn decode_checked(input: &[u8], output: &mut [u8]) -> bool {
pub(crate) unsafe fn decode_checked(input: &[u8], output: &mut [MaybeUninit<u8>]) -> bool {
debug_assert_eq!(output.len(), input.len() / 2);

let add_c6 = u8x16_splat(0xC6); // 0xFF - b'9'
Expand Down Expand Up @@ -121,7 +122,7 @@ pub(crate) unsafe fn decode_checked(input: &[u8], output: &mut [u8]) -> bool {

#[inline]
#[target_feature(enable = "simd128")]
pub(crate) unsafe fn decode_unchecked(input: &[u8], output: &mut [u8]) {
pub(crate) unsafe fn decode_unchecked(input: &[u8], output: &mut [MaybeUninit<u8>]) {
generic::decode_unchecked_unaligned_chunks(input, output, |[v0, v1]: [v128; 2]| {
let n0 = unhex(v0);
let n1 = unhex(v1);
Expand Down
9 changes: 5 additions & 4 deletions src/arch/x86.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

use super::generic;
use crate::{get_chars_table, Output};
use core::mem::MaybeUninit;

#[cfg(target_arch = "x86")]
use core::arch::x86::*;
Expand Down Expand Up @@ -171,15 +172,15 @@ unsafe fn check_chunk_sse2(chunk: __m128i) -> bool {
}

#[inline]
pub(crate) unsafe fn decode_unchecked(input: &[u8], output: &mut [u8]) {
pub(crate) unsafe fn decode_unchecked(input: &[u8], output: &mut [MaybeUninit<u8>]) {
if !has_avx2() {
return generic::decode_unchecked(input, output);
}
decode_avx2(input, output);
}

#[target_feature(enable = "avx2")]
unsafe fn decode_avx2(input: &[u8], output: &mut [u8]) {
unsafe fn decode_avx2(input: &[u8], output: &mut [MaybeUninit<u8>]) {
#[rustfmt::skip]
let mask_a = _mm256_setr_epi8(
0, -1, 2, -1, 4, -1, 6, -1, 8, -1, 10, -1, 12, -1, 14, -1,
Expand Down Expand Up @@ -237,15 +238,15 @@ unsafe fn nib2byte(a1: __m256i, b1: __m256i, a2: __m256i, b2: __m256i) -> __m256
///
/// Based on: <http://0x80.pl/notesen/2022-01-17-validating-hex-parse.html>
#[inline]
pub(crate) unsafe fn decode_checked(input: &[u8], output: &mut [u8]) -> bool {
pub(crate) unsafe fn decode_checked(input: &[u8], output: &mut [MaybeUninit<u8>]) -> bool {
if has_avx2() {
return decode_checked_avx2(input, output);
}
generic::decode_checked(input, output)
}

#[target_feature(enable = "avx2")]
unsafe fn decode_checked_avx2(input: &[u8], output: &mut [u8]) -> bool {
unsafe fn decode_checked_avx2(input: &[u8], output: &mut [MaybeUninit<u8>]) -> bool {
debug_assert_eq!(output.len(), input.len() / 2);

let add_c6 = _mm256_set1_epi8(0xC6u8 as i8); // 0xFF - b'9'
Expand Down
13 changes: 8 additions & 5 deletions src/impl_core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@

use core::mem::{self, MaybeUninit};

/// `MaybeUninit::slice_assume_init_mut`
/// Reinterprets `&mut [T]` as `&mut [MaybeUninit<T>]`.
///
/// This is safe because `MaybeUninit<T>` is guaranteed to have the same layout as `T`,
/// and an initialized `T` is always a valid `MaybeUninit<T>`.
#[inline(always)]
pub(crate) unsafe fn slice_assume_init_mut<T>(slice: &mut [MaybeUninit<T>]) -> &mut [T] {
// SAFETY: similar to safety notes for `slice_get_ref`, but we have a
// mutable reference which is also guaranteed to be valid for writes.
unsafe { &mut *(slice as *mut [MaybeUninit<T>] as *mut [T]) }
pub(crate) fn slice_as_uninit_mut<T>(slice: &mut [T]) -> &mut [MaybeUninit<T>] {
// SAFETY: `MaybeUninit<T>` has the same layout as `T`, and initialized
// memory is valid `MaybeUninit`.
unsafe { &mut *(slice as *mut [T] as *mut [MaybeUninit<T>]) }
}

/// `MaybeUninit::uninit_array`
Expand Down
51 changes: 26 additions & 25 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
extern crate alloc;

use cfg_if::cfg_if;
use core::mem::MaybeUninit;

#[cfg(feature = "alloc")]
#[allow(unused_imports)]
Expand Down Expand Up @@ -520,14 +521,14 @@ pub fn decode<T: AsRef<[u8]>>(input: T) -> Result<Vec<u8>, FromHexError> {
// Do not initialize memory since it will be entirely overwritten.
let len = input.len() / 2;
let mut output = Vec::with_capacity(len);
// SAFETY: The entire vec is never read from, and gets dropped if decoding fails.
#[allow(clippy::uninit_vec)]

// SAFETY: `decode_checked` fully writes `len` bytes on success,
// then `set_len` is called only after successful decode.
unsafe {
decode_checked(input, &mut output.spare_capacity_mut()[..len])?;
output.set_len(len);
}

// SAFETY: Lengths are checked above.
unsafe { decode_checked(input, &mut output) }.map(|()| output)
Ok(output)
}

decode_inner(input.as_ref())
Expand Down Expand Up @@ -558,7 +559,7 @@ pub fn decode<T: AsRef<[u8]>>(input: T) -> Result<Vec<u8>, FromHexError> {
/// ```
#[inline]
pub fn decode_to_slice<T: AsRef<[u8]>>(input: T, output: &mut [u8]) -> Result<(), FromHexError> {
decode_to_slice_inner(input.as_ref(), output)
decode_to_slice_inner(input.as_ref(), impl_core::slice_as_uninit_mut(output))
}

/// Decode a hex string into a fixed-length byte-array.
Expand Down Expand Up @@ -587,11 +588,9 @@ pub fn decode_to_slice<T: AsRef<[u8]>>(input: T, output: &mut [u8]) -> Result<()
pub fn decode_to_array<T: AsRef<[u8]>, const N: usize>(input: T) -> Result<[u8; N], FromHexError> {
fn decode_to_array_inner<const N: usize>(input: &[u8]) -> Result<[u8; N], FromHexError> {
let mut output = impl_core::uninit_array();
// SAFETY: The entire array is never read from.
let output_slice = unsafe { impl_core::slice_assume_init_mut(&mut output) };
// SAFETY: All elements are initialized.
decode_to_slice_inner(input, output_slice)
.map(|()| unsafe { impl_core::array_assume_init(output) })
decode_to_slice_inner(input, &mut output)?;
// SAFETY: All elements are initialized by successful decode.
Ok(unsafe { impl_core::array_assume_init(output) })
}

decode_to_array_inner(input.as_ref())
Expand All @@ -601,24 +600,26 @@ pub fn decode_to_array<T: AsRef<[u8]>, const N: usize>(input: T) -> Result<[u8;
fn encode_inner<const UPPER: bool, const PREFIX: bool>(data: &[u8]) -> String {
let capacity = PREFIX as usize * 2 + data.len() * 2;
let mut buf = Vec::<u8>::with_capacity(capacity);
// SAFETY: The entire vec is never read from, and gets dropped if decoding fails.
#[allow(clippy::uninit_vec)]
unsafe {
buf.set_len(capacity)
};
let mut output = buf.as_mut_slice();

// SAFETY: `spare_capacity_mut` returns uninitialized memory which is fully
// written to by the prefix write and `imp::encode`, then `set_len` commits.
let mut output = &mut buf.spare_capacity_mut()[..capacity];
if PREFIX {
// SAFETY: `output` is long enough.
unsafe {
*output.get_unchecked_mut(0) = b'0';
*output.get_unchecked_mut(1) = b'x';
output.get_unchecked_mut(0).write(b'0');
output.get_unchecked_mut(1).write(b'x');
output = output.get_unchecked_mut(2..);
}
}
// SAFETY: `output` is long enough (input.len() * 2).
unsafe { imp::encode::<UPPER>(data, output) };
// SAFETY: We only write only ASCII bytes.
unsafe { String::from_utf8_unchecked(buf) }
// SAFETY: `output` is long enough (data.len() * 2), `encode` fully writes all bytes,
// then `set_len` is called only after successful encode.
// We only write ASCII bytes, which are valid UTF-8.
unsafe {
imp::encode::<UPPER>(data, output);
buf.set_len(capacity);
String::from_utf8_unchecked(buf)
}
}

fn encode_to_slice_inner<const UPPER: bool>(
Expand All @@ -645,7 +646,7 @@ fn encode_to_str_inner<'o, const UPPER: bool>(
Ok(s)
}

fn decode_to_slice_inner(input: &[u8], output: &mut [u8]) -> Result<(), FromHexError> {
fn decode_to_slice_inner(input: &[u8], output: &mut [MaybeUninit<u8>]) -> Result<(), FromHexError> {
if unlikely(input.len() % 2 != 0) {
return Err(FromHexError::OddLength);
}
Expand All @@ -661,7 +662,7 @@ fn decode_to_slice_inner(input: &[u8], output: &mut [u8]) -> Result<(), FromHexE
///
/// Assumes `output.len() == input.len() / 2`.
#[inline]
unsafe fn decode_checked(input: &[u8], output: &mut [u8]) -> Result<(), FromHexError> {
unsafe fn decode_checked(input: &[u8], output: &mut [MaybeUninit<u8>]) -> Result<(), FromHexError> {
debug_assert_eq!(output.len(), input.len() / 2);

if imp::USE_CHECK_FN {
Expand Down
49 changes: 43 additions & 6 deletions src/output.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use core::fmt::{self, Write};
use core::mem::MaybeUninit;

/// Internal trait for abstracting over output buffer types.
pub(crate) trait Output {
Expand All @@ -18,13 +19,25 @@ pub(crate) trait Output {
impl Output for &mut [u8] {
#[inline]
fn write(&mut self, bytes: &[u8]) {
let src = bytes.as_ptr();
let dst = self.as_mut_ptr();
let count = bytes.len();
debug_assert!(self.len() >= count);
let this = crate::impl_core::slice_as_uninit_mut(self);
unsafe {
dst.copy_from_nonoverlapping(src, count);
*self = core::slice::from_raw_parts_mut(dst.add(count), self.len() - count);
let count = write_bytes_output_slice(this, bytes);
advance_slice(self, count);
}
}

#[inline]
fn remaining(&self) -> Option<usize> {
Some(self.len())
}
}

impl Output for &mut [MaybeUninit<u8>] {
#[inline]
fn write(&mut self, bytes: &[u8]) {
unsafe {
let count = write_bytes_output_slice(self, bytes);
advance_slice(self, count);
}
}

Expand All @@ -48,3 +61,27 @@ impl Output for &mut fmt::Formatter<'_> {
let _ = self.write_char(byte as char);
}
}

/// # Safety
///
/// Caller must guarantee `output.len() >= bytes.len()`.
#[inline(always)]
unsafe fn write_bytes_output_slice(output: &mut [MaybeUninit<u8>], bytes: &[u8]) -> usize {
let src = bytes.as_ptr().cast::<MaybeUninit<u8>>();
let dst = output.as_mut_ptr();
let count = bytes.len();
debug_assert!(output.len() >= count);
// SAFETY: Caller guarantees `output` is at least `count` bytes long.
unsafe { dst.copy_from_nonoverlapping(src, count) };
count
}

/// Safety: Caller must guarantee `slice` is long enough, and that `slice` is not concurrently accessed.
#[inline(always)]
unsafe fn advance_slice<T>(slice: &mut &mut [T], count: usize) {
debug_assert!(slice.len() >= count);
let len = slice.len();
let ptr = slice.as_mut_ptr();
// SAFETY: Caller must guarantee `slice` is long enough, and that `slice` is not concurrently accessed.
*slice = core::slice::from_raw_parts_mut(ptr.add(count), len - count);
}
Loading