diff --git a/include/simdjson/arm64/stringparsing_defs.h b/include/simdjson/arm64/stringparsing_defs.h index 30d02faff..c70bea25c 100644 --- a/include/simdjson/arm64/stringparsing_defs.h +++ b/include/simdjson/arm64/stringparsing_defs.h @@ -17,6 +17,7 @@ using namespace simd; struct backslash_and_quote { public: static constexpr uint32_t BYTES_PROCESSED = 32; + // We only copy if dst is non-null. simdjson_inline static backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst); simdjson_inline bool has_quote_first() { return ((bs_bits - 1) & quote_bits) != 0; } @@ -34,8 +35,10 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "backslash and quote finder must process fewer than SIMDJSON_PADDING bytes"); simd8 v0(src); simd8 v1(src + sizeof(v0)); - v0.store(dst); - v1.store(dst + sizeof(v0)); + if(dst != nullptr) { + v0.store(dst); + v1.store(dst + sizeof(v0)); + } // Getting a 64-bit bitmask is much cheaper than multiple 16-bit bitmasks on ARM; therefore, we // smash them together into a 64-byte mask and get the bitmask from there. diff --git a/include/simdjson/fallback/stringparsing_defs.h b/include/simdjson/fallback/stringparsing_defs.h index 64f23c4b0..f82c38f82 100644 --- a/include/simdjson/fallback/stringparsing_defs.h +++ b/include/simdjson/fallback/stringparsing_defs.h @@ -13,6 +13,7 @@ namespace { struct backslash_and_quote { public: static constexpr uint32_t BYTES_PROCESSED = 1; + // We only copy if dst is non-null. simdjson_inline static backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst); simdjson_inline bool has_quote_first() { return c == '"'; } @@ -25,7 +26,9 @@ public: simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uint8_t *src, uint8_t *dst) { // store to dest unconditionally - we can overwrite the bits we don't like later - dst[0] = src[0]; + if(dst != nullptr) { + dst[0] = src[0]; + } return { src[0] }; } diff --git a/include/simdjson/generic/dom_parser_implementation.h b/include/simdjson/generic/dom_parser_implementation.h index e51d2c127..6074b9bd3 100644 --- a/include/simdjson/generic/dom_parser_implementation.h +++ b/include/simdjson/generic/dom_parser_implementation.h @@ -40,6 +40,7 @@ public: simdjson_warn_unused error_code stage1(const uint8_t *buf, size_t len, stage1_mode partial) noexcept final; simdjson_warn_unused error_code stage2(dom::document &doc) noexcept final; simdjson_warn_unused error_code stage2_next(dom::document &doc) noexcept final; + simdjson_warn_unused std::pair parse_string_if_needed(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept final; simdjson_warn_unused uint8_t *parse_string(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept final; simdjson_warn_unused uint8_t *parse_wobbly_string(const uint8_t *src, uint8_t *dst) const noexcept final; inline simdjson_warn_unused error_code set_capacity(size_t capacity) noexcept final; diff --git a/include/simdjson/generic/ondemand/parser-inl.h b/include/simdjson/generic/ondemand/parser-inl.h index 7d935f58f..63dbd05bc 100644 --- a/include/simdjson/generic/ondemand/parser-inl.h +++ b/include/simdjson/generic/ondemand/parser-inl.h @@ -169,11 +169,17 @@ simdjson_inline void parser::set_max_capacity(size_t max_capacity) noexcept { } simdjson_inline simdjson_warn_unused simdjson_result parser::unescape(raw_json_string in, uint8_t *&dst, bool allow_replacement) const noexcept { - uint8_t *end = implementation->parse_string(in.buf, dst, allow_replacement); + std::pair result = implementation->parse_string_if_needed(in.buf, dst, allow_replacement); + const uint8_t *end = result.first; + bool copied = result.second; if (!end) { return STRING_ERROR; } - std::string_view result(reinterpret_cast(dst), end-dst); - dst = end; - return result; + if(copied) { + std::string_view strresult(reinterpret_cast(dst), end-dst); + dst = const_cast(end); + return strresult; + } + // fast path, no copy was made!!! + return std::string_view(reinterpret_cast(in.buf), end-in.buf); } simdjson_inline simdjson_warn_unused simdjson_result parser::unescape_wobbly(raw_json_string in, uint8_t *&dst) const noexcept { diff --git a/include/simdjson/haswell/stringparsing_defs.h b/include/simdjson/haswell/stringparsing_defs.h index f896a10e2..0e6f36429 100644 --- a/include/simdjson/haswell/stringparsing_defs.h +++ b/include/simdjson/haswell/stringparsing_defs.h @@ -17,6 +17,7 @@ using namespace simd; struct backslash_and_quote { public: static constexpr uint32_t BYTES_PROCESSED = 32; + // We only copy if dst is non-null. simdjson_inline static backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst); simdjson_inline bool has_quote_first() { return ((bs_bits - 1) & quote_bits) != 0; } @@ -34,7 +35,9 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "backslash and quote finder must process fewer than SIMDJSON_PADDING bytes"); simd8 v(src); // store to dest unconditionally - we can overwrite the bits we don't like later - v.store(dst); + if(dst != nullptr) { + v.store(dst); + } return { static_cast((v == '\\').to_bitmask()), // bs_bits static_cast((v == '"').to_bitmask()), // quote_bits diff --git a/include/simdjson/icelake/stringparsing_defs.h b/include/simdjson/icelake/stringparsing_defs.h index 4cc582737..64c190de5 100644 --- a/include/simdjson/icelake/stringparsing_defs.h +++ b/include/simdjson/icelake/stringparsing_defs.h @@ -17,6 +17,7 @@ using namespace simd; struct backslash_and_quote { public: static constexpr uint32_t BYTES_PROCESSED = 64; + // We only copy if dst is non-null. simdjson_inline static backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst); simdjson_inline bool has_quote_first() { return ((bs_bits - 1) & quote_bits) != 0; } @@ -34,7 +35,9 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "backslash and quote finder must process fewer than SIMDJSON_PADDING bytes"); simd8 v(src); // store to dest unconditionally - we can overwrite the bits we don't like later - v.store(dst); + if(dst != nullptr) { + v.store(dst); + } return { static_cast(v == '\\'), // bs_bits static_cast(v == '"'), // quote_bits diff --git a/include/simdjson/internal/dom_parser_implementation.h b/include/simdjson/internal/dom_parser_implementation.h index a93fe38ff..cad0321bf 100644 --- a/include/simdjson/internal/dom_parser_implementation.h +++ b/include/simdjson/internal/dom_parser_implementation.h @@ -4,6 +4,7 @@ #include "simdjson/base.h" #include "simdjson/error.h" #include +#include namespace simdjson { @@ -102,6 +103,24 @@ public: */ simdjson_warn_unused virtual error_code stage2_next(dom::document &doc) noexcept = 0; + /** + * Unescape a valid UTF-8 string from src to dst, stopping at a final unescaped quote. There + * must be an unescaped quote terminating the string. It returns the final output + * position as pointer. In case of error (e.g., the string has bad escaped codes), + * then null_ptr is returned. If no escaping was required, then no copy is made. + * It is assumed that the output buffer is large + * enough to store the unescapedstring + SIMDJSON_PADDING bytes. + * + * Overridden by each implementation. + * + * @param str pointer to the beginning of a valid UTF-8 JSON string, must end with an unescaped quote. + * @param dst pointer to a destination buffer, it must point a region in memory of sufficient size. + * @param allow_replacement whether we allow a replacement character when the UTF-8 contains unmatched surrogate pairs. + * @return end of the of the written region (exclusive) or nullptr in case of error coupled with a Boolean telling you if a copy was made + */ + simdjson_warn_unused virtual std::pair parse_string_if_needed(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept = 0; + + /** * Unescape a valid UTF-8 string from src to dst, stopping at a final unescaped quote. There * must be an unescaped quote terminating the string. It returns the final output diff --git a/include/simdjson/lasx/stringparsing_defs.h b/include/simdjson/lasx/stringparsing_defs.h index fe7a7430e..74714e928 100644 --- a/include/simdjson/lasx/stringparsing_defs.h +++ b/include/simdjson/lasx/stringparsing_defs.h @@ -17,6 +17,7 @@ using namespace simd; struct backslash_and_quote { public: static constexpr uint32_t BYTES_PROCESSED = 32; + // We only copy if dst is non-null. simdjson_inline static backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst); simdjson_inline bool has_quote_first() { return ((bs_bits - 1) & quote_bits) != 0; } @@ -33,7 +34,9 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin // SIMDJSON_PADDING of padding static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "backslash and quote finder must process fewer than SIMDJSON_PADDING bytes"); simd8 v(src); - v.store(dst); + if(dst != nullptr) { + v.store(dst); + } return { static_cast((v == '\\').to_bitmask()), // bs_bits static_cast((v == '"').to_bitmask()), // quote_bits diff --git a/include/simdjson/lsx/stringparsing_defs.h b/include/simdjson/lsx/stringparsing_defs.h index af493dc55..36247f1ce 100644 --- a/include/simdjson/lsx/stringparsing_defs.h +++ b/include/simdjson/lsx/stringparsing_defs.h @@ -17,6 +17,7 @@ using namespace simd; struct backslash_and_quote { public: static constexpr uint32_t BYTES_PROCESSED = 32; + // We only copy if dst is non-null. simdjson_inline static backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst); simdjson_inline bool has_quote_first() { return ((bs_bits - 1) & quote_bits) != 0; } @@ -34,8 +35,10 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "backslash and quote finder must process fewer than SIMDJSON_PADDING bytes"); simd8 v0(src); simd8 v1(src + sizeof(v0)); - v0.store(dst); - v1.store(dst + sizeof(v0)); + if(dst != nullptr) { + v0.store(dst); + v1.store(dst + sizeof(v0)); + } // Getting a 64-bit bitmask is much cheaper than multiple 16-bit bitmasks on LSX; therefore, we // smash them together into a 64-byte mask and get the bitmask from there. diff --git a/include/simdjson/ppc64/stringparsing_defs.h b/include/simdjson/ppc64/stringparsing_defs.h index 82e442435..286787fc9 100644 --- a/include/simdjson/ppc64/stringparsing_defs.h +++ b/include/simdjson/ppc64/stringparsing_defs.h @@ -18,6 +18,7 @@ struct backslash_and_quote { public: static constexpr uint32_t BYTES_PROCESSED = 32; simdjson_inline static backslash_and_quote + // We only copy if dst is non-null. copy_and_find(const uint8_t *src, uint8_t *dst); simdjson_inline bool has_quote_first() { @@ -44,8 +45,10 @@ backslash_and_quote::copy_and_find(const uint8_t *src, uint8_t *dst) { "SIMDJSON_PADDING bytes"); simd8 v0(src); simd8 v1(src + sizeof(v0)); - v0.store(dst); - v1.store(dst + sizeof(v0)); + if(dst != nullptr) { + v0.store(dst); + v1.store(dst + sizeof(v0)); + } // Getting a 64-bit bitmask is much cheaper than multiple 16-bit bitmasks on // PPC; therefore, we smash them together into a 64-byte mask and get the diff --git a/include/simdjson/westmere/stringparsing_defs.h b/include/simdjson/westmere/stringparsing_defs.h index 439f19cbc..d0fd92a53 100644 --- a/include/simdjson/westmere/stringparsing_defs.h +++ b/include/simdjson/westmere/stringparsing_defs.h @@ -14,6 +14,7 @@ using namespace simd; struct backslash_and_quote { public: static constexpr uint32_t BYTES_PROCESSED = 32; + // We only copy if dst is non-null. simdjson_inline static backslash_and_quote copy_and_find(const uint8_t *src, uint8_t *dst); simdjson_inline bool has_quote_first() { return ((bs_bits - 1) & quote_bits) != 0; } @@ -31,8 +32,10 @@ simdjson_inline backslash_and_quote backslash_and_quote::copy_and_find(const uin static_assert(SIMDJSON_PADDING >= (BYTES_PROCESSED - 1), "backslash and quote finder must process fewer than SIMDJSON_PADDING bytes"); simd8 v0(src); simd8 v1(src + 16); - v0.store(dst); - v1.store(dst + 16); + if(dst != nullptr) { + v0.store(dst); + v1.store(dst + 16); + } uint64_t bs_and_quote = simd8x64(v0 == '\\', v1 == '\\', v0 == '"', v1 == '"').to_bitmask(); return { uint32_t(bs_and_quote), // bs_bits diff --git a/src/arm64.cpp b/src/arm64.cpp index 436757d53..4f6994e3f 100644 --- a/src/arm64.cpp +++ b/src/arm64.cpp @@ -150,6 +150,10 @@ simdjson_warn_unused error_code dom_parser_implementation::stage2_next(dom::docu return stage2::tape_builder::parse_document(*this, _doc); } +simdjson_warn_unused std::pair dom_parser_implementation::parse_string_if_needed(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept { + return arm64::stringparsing::parse_string_if_needed(src, dst, allow_replacement); +} + simdjson_warn_unused uint8_t *dom_parser_implementation::parse_string(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept { return arm64::stringparsing::parse_string(src, dst, allow_replacement); } diff --git a/src/fallback.cpp b/src/fallback.cpp index f8e87be06..b40af5139 100644 --- a/src/fallback.cpp +++ b/src/fallback.cpp @@ -388,6 +388,10 @@ simdjson_warn_unused error_code dom_parser_implementation::stage2_next(dom::docu return stage2::tape_builder::parse_document(*this, _doc); } +simdjson_warn_unused std::pair dom_parser_implementation::parse_string_if_needed(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept { + return fallback::stringparsing::parse_string_if_needed(src, dst, allow_replacement); +} + simdjson_warn_unused uint8_t *dom_parser_implementation::parse_string(const uint8_t *src, uint8_t *dst, bool replacement_char) const noexcept { return fallback::stringparsing::parse_string(src, dst, replacement_char); } diff --git a/src/generic/stage2/stringparsing.h b/src/generic/stage2/stringparsing.h index 07a4daf50..ab4970519 100644 --- a/src/generic/stage2/stringparsing.h +++ b/src/generic/stage2/stringparsing.h @@ -1,3 +1,5 @@ +#include +#include #ifndef SIMDJSON_SRC_GENERIC_STAGE2_STRINGPARSING_H #ifndef SIMDJSON_CONDITIONAL_INCLUDE @@ -138,6 +140,93 @@ simdjson_inline bool handle_unicode_codepoint_wobbly(const uint8_t **src_ptr, return offset > 0; } +/** + * Unescape a valid UTF-8 string from src to dst, stopping at a final unescaped quote. + * If there is no need for unescaping, it avoids copying the string. + * There + * must be an unescaped quote terminating the string. It returns the final output + * position as pointer. In case of error (e.g., the string has bad escaped codes), + * then null_ptr is returned. It is assumed that the output buffer is large + * enough. E.g., if src points at 'joe"', then dst needs to have four free bytes + + * SIMDJSON_PADDING bytes. + */ +simdjson_warn_unused simdjson_inline std::pair parse_string_if_needed(const uint8_t *src, uint8_t *dst, bool allow_replacement) { + const uint8_t *srcinit = src; + while (1) { + // Find the backslash and quote in them, we pass null because we do not copy. + auto bs_quote = backslash_and_quote::copy_and_find(src, nullptr); + // If the next thing is the end quote, copy and return + if (bs_quote.has_quote_first()) { + // we encountered quotes first. + return {src + bs_quote.quote_index(), false}; + } + if (bs_quote.has_backslash()) { + std::memcpy(dst, srcinit, src - srcinit + backslash_and_quote::BYTES_PROCESSED); + dst += src - srcinit; + auto bs_dist = bs_quote.backslash_index(); + uint8_t escape_char = src[bs_dist + 1]; + if (escape_char == 'u') { + src += bs_dist; + dst += bs_dist; + if (!handle_unicode_codepoint(&src, &dst, allow_replacement)) { + return {nullptr, true}; + } + } else { + uint8_t escape_result = escape_map[escape_char]; + if (escape_result == 0u) { + return {nullptr, true}; + } + dst[bs_dist] = escape_result; + src += bs_dist + 2; + dst += bs_dist + 1; + } + break; + } else { + src += backslash_and_quote::BYTES_PROCESSED; + } + } + while (1) { + // Copy the next n bytes, and find the backslash and quote in them. + auto bs_quote = backslash_and_quote::copy_and_find(src, dst); + // If the next thing is the end quote, copy and return + if (bs_quote.has_quote_first()) { + // we encountered quotes first. Move dst to point to quotes and exit + return {dst + bs_quote.quote_index(), true}; + } + if (bs_quote.has_backslash()) { + /* find out where the backspace is */ + auto bs_dist = bs_quote.backslash_index(); + uint8_t escape_char = src[bs_dist + 1]; + /* we encountered backslash first. Handle backslash */ + if (escape_char == 'u') { + /* move src/dst up to the start; they will be further adjusted + within the unicode codepoint handling code. */ + src += bs_dist; + dst += bs_dist; + if (!handle_unicode_codepoint(&src, &dst, allow_replacement)) { + return {nullptr, true}; + } + } 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 */ + uint8_t escape_result = escape_map[escape_char]; + if (escape_result == 0u) { + return {nullptr, true}; /* bogus escape value is an error */ + } + dst[bs_dist] = escape_result; + src += bs_dist + 2; + dst += bs_dist + 1; + } + } else { + /* they are the same. Since they can't co-occur, it means we + * encountered neither. */ + src += backslash_and_quote::BYTES_PROCESSED; + dst += backslash_and_quote::BYTES_PROCESSED; + } + } +} /** * Unescape a valid UTF-8 string from src to dst, stopping at a final unescaped quote. There diff --git a/src/haswell.cpp b/src/haswell.cpp index f721cac8b..18b296858 100644 --- a/src/haswell.cpp +++ b/src/haswell.cpp @@ -147,6 +147,10 @@ simdjson_warn_unused error_code dom_parser_implementation::stage2_next(dom::docu return stage2::tape_builder::parse_document(*this, _doc); } +simdjson_warn_unused std::pair dom_parser_implementation::parse_string_if_needed(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept { + return haswell::stringparsing::parse_string_if_needed(src, dst, allow_replacement); +} + simdjson_warn_unused uint8_t *dom_parser_implementation::parse_string(const uint8_t *src, uint8_t *dst, bool replacement_char) const noexcept { return haswell::stringparsing::parse_string(src, dst, replacement_char); } diff --git a/src/icelake.cpp b/src/icelake.cpp index 8ec08c69c..636779f13 100644 --- a/src/icelake.cpp +++ b/src/icelake.cpp @@ -193,6 +193,10 @@ simdjson_warn_unused error_code dom_parser_implementation::stage2_next(dom::docu return stage2::tape_builder::parse_document(*this, _doc); } +simdjson_warn_unused std::pair dom_parser_implementation::parse_string_if_needed(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept { + return icelake::stringparsing::parse_string_if_needed(src, dst, allow_replacement); +} + simdjson_warn_unused uint8_t *dom_parser_implementation::parse_string(const uint8_t *src, uint8_t *dst, bool replacement_char) const noexcept { return icelake::stringparsing::parse_string(src, dst, replacement_char); } diff --git a/src/lasx.cpp b/src/lasx.cpp index 3be3f080e..a2fd374d9 100644 --- a/src/lasx.cpp +++ b/src/lasx.cpp @@ -110,6 +110,10 @@ simdjson_warn_unused error_code dom_parser_implementation::stage2_next(dom::docu return stage2::tape_builder::parse_document(*this, _doc); } +simdjson_warn_unused std::pair dom_parser_implementation::parse_string_if_needed(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept { + return lasx::stringparsing::parse_string_if_needed(src, dst, allow_replacement); +} + simdjson_warn_unused uint8_t *dom_parser_implementation::parse_string(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept { return lasx::stringparsing::parse_string(src, dst, allow_replacement); } diff --git a/src/lsx.cpp b/src/lsx.cpp index 99af664a5..8b299b082 100644 --- a/src/lsx.cpp +++ b/src/lsx.cpp @@ -114,6 +114,10 @@ simdjson_warn_unused error_code dom_parser_implementation::stage2_next(dom::docu return stage2::tape_builder::parse_document(*this, _doc); } +simdjson_warn_unused std::pair dom_parser_implementation::parse_string_if_needed(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept { + return lsx::stringparsing::parse_string_if_needed(src, dst, allow_replacement); +} + simdjson_warn_unused uint8_t *dom_parser_implementation::parse_string(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept { return lsx::stringparsing::parse_string(src, dst, allow_replacement); } diff --git a/src/ppc64.cpp b/src/ppc64.cpp index 63606380c..dd3dcd4fc 100644 --- a/src/ppc64.cpp +++ b/src/ppc64.cpp @@ -120,6 +120,10 @@ simdjson_warn_unused error_code dom_parser_implementation::stage2_next(dom::docu return stage2::tape_builder::parse_document(*this, _doc); } +simdjson_warn_unused std::pair dom_parser_implementation::parse_string_if_needed(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept { + return ppc64::stringparsing::parse_string_if_needed(src, dst, allow_replacement); +} + simdjson_warn_unused uint8_t *dom_parser_implementation::parse_string(const uint8_t *src, uint8_t *dst, bool replacement_char) const noexcept { return ppc64::stringparsing::parse_string(src, dst, replacement_char); } diff --git a/src/westmere.cpp b/src/westmere.cpp index 538db42f8..b87255041 100644 --- a/src/westmere.cpp +++ b/src/westmere.cpp @@ -152,6 +152,10 @@ simdjson_warn_unused error_code dom_parser_implementation::stage2_next(dom::docu return stage2::tape_builder::parse_document(*this, _doc); } +simdjson_warn_unused std::pair dom_parser_implementation::parse_string_if_needed(const uint8_t *src, uint8_t *dst, bool allow_replacement) const noexcept { + return westmere::stringparsing::parse_string_if_needed(src, dst, allow_replacement); +} + simdjson_warn_unused uint8_t *dom_parser_implementation::parse_string(const uint8_t *src, uint8_t *dst, bool replacement_char) const noexcept { return westmere::stringparsing::parse_string(src, dst, replacement_char); }