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
2 changes: 1 addition & 1 deletion .github/workflows/big-endian.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
steps:
- uses: actions/checkout@v3

- uses: dtolnay/rust-toolchain@1.88
- uses: dtolnay/rust-toolchain@1.89

- name: Install cross
uses: taiki-e/install-action@v2
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/quality.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
steps:
- uses: actions/checkout@v3

- uses: dtolnay/rust-toolchain@1.88 # do clippy chekcs with the minimum supported version
- uses: dtolnay/rust-toolchain@1.89 # do clippy chekcs with the minimum supported version
with:
components: rustfmt, clippy

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
steps:
- uses: actions/checkout@v3

- uses: dtolnay/rust-toolchain@1.88
- uses: dtolnay/rust-toolchain@1.89
with:
components: llvm-tools-preview

Expand Down Expand Up @@ -120,7 +120,7 @@ jobs:
steps:
- uses: actions/checkout@v3

- uses: dtolnay/rust-toolchain@1.88
- uses: dtolnay/rust-toolchain@1.89
with:
targets: wasm32-wasip1

Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ description = "High performance JSON parser based on a port of simdjson"
repository = "https://github.com/simd-lite/simd-json"
readme = "README.md"
documentation = "https://docs.rs/simd-json"
rust-version = "1.88"
rust-version = "1.89"

[dependencies]
simdutf8 = { version = "0.1.4", features = ["public_imp", "aarch64_neon"] }
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ However, in some design decisions—such as parsing to a DOM or a tape—ergonom
performance. In other places Rust makes it harder to achieve the same level of performance.

To take advantage of this library your system needs to support SIMD instructions. On `x86`, it will
select the best available supported instruction set (`avx2` or `sse4.2`) when the `runtime-detection` feature
select the best available supported instruction set (`avx512bw`, `avx2` or `sse4.2`) when the `runtime-detection` feature
is enabled (default). On `aarch64` this library uses the `NEON` instruction set. On `wasm` this library uses
the `simd128` instruction set when available. When no supported SIMD instructions are found, this library will use a
fallback implementation, but this is significantly slower.
Expand Down Expand Up @@ -75,7 +75,7 @@ for internal configuration and testing.
### `runtime-detection` (default)

This feature allows selecting the optimal algorithm based on available features during runtime. It has no effect on
non-`x86` platforms. When neither `AVX2` nor `SSE4.2` is supported, it will fall back to a native Rust implementation.
non-`x86` platforms. When neither one of `AVX512BW` `AVX2` `SSE4.2` is supported, it will fall back to a native Rust implementation.

Disabling this feature (with `default-features = false`) **and** setting `RUSTFLAGS="-C target-cpu=native` will result
in better performance but the resulting binary will not be portable across `x86` processors.
Expand Down
177 changes: 177 additions & 0 deletions src/impls/avx512bw/deser.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64 as arch;
use std::arch::x86_64::{
__m512i, _mm512_cmpeq_epu8_mask, _mm512_loadu_si512, _mm512_set1_epi8, _mm512_storeu_si512,
};

use crate::{
Deserializer, Result, SillyWrapper,
error::ErrorType,
safer_unchecked::GetSaferUnchecked,
stringparse::{ESCAPE_MAP, handle_unicode_codepoint},
};

#[target_feature(enable = "avx512bw")]
#[allow(
clippy::if_not_else,
clippy::cast_possible_wrap,
clippy::too_many_lines
)]
#[cfg_attr(not(feature = "no-inline"), inline)]
pub(crate) unsafe fn parse_str<'invoke, 'de>(
input: SillyWrapper<'de>,
data: &'invoke [u8],
buffer: &'invoke mut [u8],
mut idx: usize,
) -> Result<&'de str> {
unsafe {
use ErrorType::{InvalidEscape, InvalidUnicodeCodepoint};

let input = input.input;
// Add 1 to skip the initial "
idx += 1;
//let mut read: usize = 0;

// we include the terminal '"' so we know where to end
// This is safe since we check sub's length in the range access above and only
// create sub sliced form sub to `sub.len()`.

let src: &[u8] = data.get_kinda_unchecked(idx..);
let mut src_i: usize = 0;
let mut len = src_i;
loop {
// _mm512_loadu_si512 does not require alignment
#[allow(clippy::cast_ptr_alignment)]
let v: __m512i = _mm512_loadu_si512(src.as_ptr().add(src_i).cast::<__m512i>());

// store to dest unconditionally - we can overwrite the bits we don't like
// later
let bs_bits: u64 = _mm512_cmpeq_epu8_mask(v, _mm512_set1_epi8(b'\\' as i8));

let quote_bits = _mm512_cmpeq_epu8_mask(v, _mm512_set1_epi8(b'"' as i8));

if (bs_bits.wrapping_sub(1) & quote_bits) != 0 {
// we encountered quotes first. Move dst to point to quotes and exit
// find out where the quote is...
let quote_dist: u32 = quote_bits.trailing_zeros();

///////////////////////
// Above, check for overflow in case someone has a crazy string (>=4GB?)
// But only add the overflow check when the document itself exceeds 4GB
// Currently unneeded because we refuse to parse docs larger or equal to 4GB.
////////////////////////

// we advance the point, accounting for the fact that we have a NULl termination

len += quote_dist as usize;
let v =
std::str::from_utf8_unchecked(std::slice::from_raw_parts(input.add(idx), len));
return Ok(v);

// we compare the pointers since we care if they are 'at the same spot'
// not if they are the same value
}
if (quote_bits.wrapping_sub(1) & bs_bits) == 0 {
// they are the same. Since they can't co-occur, it means we encountered
// neither.
src_i += 64;
len += 64;
} else {
// Move to the 'bad' character
let bs_dist: u32 = bs_bits.trailing_zeros();
len += bs_dist as usize;
src_i += bs_dist as usize;
break;
}
}

let mut dst_i: usize = 0;

// To be more conform with upstream
loop {
// _mm512_loadu_si512 does not require alignment
#[allow(clippy::cast_ptr_alignment)]
let v: __m512i = _mm512_loadu_si512(src.as_ptr().add(src_i).cast::<__m512i>());

#[allow(clippy::cast_ptr_alignment)]
_mm512_storeu_si512(buffer.as_mut_ptr().add(dst_i).cast::<__m512i>(), v);

// store to dest unconditionally - we can overwrite the bits we don't like
// later
let bs_bits: u64 = _mm512_cmpeq_epu8_mask(v, _mm512_set1_epi8(b'\\' as i8));

let quote_bits = _mm512_cmpeq_epu8_mask(v, _mm512_set1_epi8(b'"' as i8));
if (bs_bits.wrapping_sub(1) & quote_bits) != 0 {
// we encountered quotes first. Move dst to point to quotes and exit
// find out where the quote is...
let quote_dist: u32 = quote_bits.trailing_zeros();

///////////////////////
// Above, check for overflow in case someone has a crazy string (>=4GB?)
// But only add the overflow check when the document itself exceeds 4GB
// Currently unneeded because we refuse to parse docs larger or equal to 4GB.
////////////////////////

// we advance the point, accounting for the fact that we have a NULl termination

dst_i += quote_dist as usize;
input
.add(idx + len)
.copy_from_nonoverlapping(buffer.as_ptr(), dst_i);
let v = std::str::from_utf8_unchecked(std::slice::from_raw_parts(
input.add(idx),
len + dst_i,
));
return Ok(v);

// we compare the pointers since we care if they are 'at the same spot'
// not if they are the same value
}
if (quote_bits.wrapping_sub(1) & bs_bits) != 0 {
// find out where the backspace is
let bs_dist: u32 = bs_bits.trailing_zeros();
let escape_char: u8 = *src.get_kinda_unchecked(src_i + bs_dist as usize + 1);
// we encountered backslash first. Handle backslash
if escape_char == b'u' {
// move src/dst up to the start; they will be further adjusted
// within the unicode codepoint handling code.
src_i += bs_dist as usize;
dst_i += bs_dist as usize;
let (o, s) = handle_unicode_codepoint(
src.get_kinda_unchecked(src_i..),
buffer.get_kinda_unchecked_mut(dst_i..),
)
.map_err(|_| Deserializer::error_c(src_i, 'u', InvalidUnicodeCodepoint))?;

if o == 0 {
return Err(Deserializer::error_c(src_i, 'u', InvalidUnicodeCodepoint));
}
// We moved o steps forward at the destination and 6 on the source
src_i += s;
dst_i += o;
} else {
// simple 1:1 conversion. Will eat bs_dist+2 characters in input and
// write bs_dist+1 characters to output
// note this may reach beyond the part of the buffer we've actually
// seen. I think this is ok
let escape_result: u8 = *ESCAPE_MAP.get_kinda_unchecked(escape_char as usize);
if escape_result == 0 {
return Err(Deserializer::error_c(
src_i,
escape_char as char,
InvalidEscape,
));
}
*buffer.get_kinda_unchecked_mut(dst_i + bs_dist as usize) = escape_result;
src_i += bs_dist as usize + 2;
dst_i += bs_dist as usize + 1;
}
} else {
// they are the same. Since they can't co-occur, it means we encountered
// neither.
src_i += 64;
dst_i += 64;
}
}
}
}
6 changes: 6 additions & 0 deletions src/impls/avx512bw/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#![allow(unused_imports, dead_code)]
mod deser;
mod stage1;

pub(crate) use deser::parse_str;
pub(crate) use stage1::SimdInput;
Loading