#include #include #include #include #include #ifndef SIMDJSON_GENERIC_STRING_BUILDER_INL_H #ifndef SIMDJSON_CONDITIONAL_INCLUDE #define SIMDJSON_GENERIC_STRING_BUILDER_INL_H #include "simdjson/generic/builder/json_string_builder.h" #endif // SIMDJSON_CONDITIONAL_INCLUDE /* * Empirically, we have found that an inlined optimization is important for * performance. The following macros are not ideal. We should find a better * way to inline the code. */ #if defined(__SSE2__) || defined(__x86_64__) || defined(__x86_64) || \ (defined(_M_AMD64) || defined(_M_X64) || \ (defined(_M_IX86_FP) && _M_IX86_FP == 2)) #ifndef SIMDJSON_EXPERIMENTAL_HAS_SSE2 #define SIMDJSON_EXPERIMENTAL_HAS_SSE2 1 #endif #endif #if defined(__aarch64__) || defined(_M_ARM64) #ifndef SIMDJSON_EXPERIMENTAL_HAS_NEON #define SIMDJSON_EXPERIMENTAL_HAS_NEON 1 #endif #endif #if defined(__loongarch_sx) #ifndef SIMDJSON_EXPERIMENTAL_HAS_LSX #define SIMDJSON_EXPERIMENTAL_HAS_LSX 1 #endif #endif #if defined(__riscv_v_intrinsic) && __riscv_v_intrinsic >= 11000 && \ defined(__riscv_vector) #ifndef SIMDJSON_EXPERIMENTAL_HAS_RVV #define SIMDJSON_EXPERIMENTAL_HAS_RVV 1 #endif #endif #if (defined(__PPC64__) || defined(_M_PPC64)) && defined(__ALTIVEC__) #ifndef SIMDJSON_EXPERIMENTAL_HAS_PPC64 #define SIMDJSON_EXPERIMENTAL_HAS_PPC64 1 #endif #endif #if SIMDJSON_EXPERIMENTAL_HAS_NEON #include #ifdef _MSC_VER #include #endif #endif #if SIMDJSON_EXPERIMENTAL_HAS_SSE2 #include #ifdef _MSC_VER #include #endif #endif #if SIMDJSON_EXPERIMENTAL_HAS_LSX #include #endif #if SIMDJSON_EXPERIMENTAL_HAS_RVV #include #endif #if SIMDJSON_EXPERIMENTAL_HAS_PPC64 #include #ifdef bool #undef bool #endif #ifdef vector #undef vector #endif #endif namespace simdjson { namespace SIMDJSON_IMPLEMENTATION { namespace builder { static SIMDJSON_CONSTEXPR_LAMBDA std::array json_quotable_character = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; /** A possible SWAR implementation of has_json_escapable_byte. It is not used because it is slower than the current implementation. It is kept here for reference (to show that we tried it). inline bool has_json_escapable_byte(uint64_t x) { uint64_t is_ascii = 0x8080808080808080ULL & ~x; uint64_t xor2 = x ^ 0x0202020202020202ULL; uint64_t lt32_or_eq34 = xor2 - 0x2121212121212121ULL; uint64_t sub92 = x ^ 0x5C5C5C5C5C5C5C5CULL; uint64_t eq92 = (sub92 - 0x0101010101010101ULL); return ((lt32_or_eq34 | eq92) & is_ascii) != 0; } **/ SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline bool simple_needs_escaping(std::string_view v) { for (char c : v) { // a table lookup is faster than a series of comparisons if (json_quotable_character[static_cast(c)]) { return true; } } return false; } #if SIMDJSON_EXPERIMENTAL_HAS_NEON simdjson_inline bool fast_needs_escaping(std::string_view view) { if (view.size() < 16) { return simple_needs_escaping(view); } size_t i = 0; uint8x16_t running = vdupq_n_u8(0); uint8x16_t v34 = vdupq_n_u8(34); uint8x16_t v92 = vdupq_n_u8(92); for (; i + 15 < view.size(); i += 16) { uint8x16_t word = vld1q_u8((const uint8_t *)view.data() + i); running = vorrq_u8(running, vceqq_u8(word, v34)); running = vorrq_u8(running, vceqq_u8(word, v92)); running = vorrq_u8(running, vcltq_u8(word, vdupq_n_u8(32))); } if (i < view.size()) { uint8x16_t word = vld1q_u8((const uint8_t *)view.data() + view.length() - 16); running = vorrq_u8(running, vceqq_u8(word, v34)); running = vorrq_u8(running, vceqq_u8(word, v92)); running = vorrq_u8(running, vcltq_u8(word, vdupq_n_u8(32))); } return vmaxvq_u32(vreinterpretq_u32_u8(running)) != 0; } #elif SIMDJSON_EXPERIMENTAL_HAS_SSE2 simdjson_inline bool fast_needs_escaping(std::string_view view) { if (view.size() < 16) { return simple_needs_escaping(view); } size_t i = 0; __m128i running = _mm_setzero_si128(); for (; i + 15 < view.size(); i += 16) { __m128i word = _mm_loadu_si128(reinterpret_cast(view.data() + i)); running = _mm_or_si128(running, _mm_cmpeq_epi8(word, _mm_set1_epi8(34))); running = _mm_or_si128(running, _mm_cmpeq_epi8(word, _mm_set1_epi8(92))); running = _mm_or_si128( running, _mm_cmpeq_epi8(_mm_subs_epu8(word, _mm_set1_epi8(31)), _mm_setzero_si128())); } if (i < view.size()) { __m128i word = _mm_loadu_si128( reinterpret_cast(view.data() + view.length() - 16)); running = _mm_or_si128(running, _mm_cmpeq_epi8(word, _mm_set1_epi8(34))); running = _mm_or_si128(running, _mm_cmpeq_epi8(word, _mm_set1_epi8(92))); running = _mm_or_si128( running, _mm_cmpeq_epi8(_mm_subs_epu8(word, _mm_set1_epi8(31)), _mm_setzero_si128())); } return _mm_movemask_epi8(running) != 0; } #elif SIMDJSON_EXPERIMENTAL_HAS_LSX simdjson_inline bool fast_needs_escaping(std::string_view view) { if (view.size() < 16) { return simple_needs_escaping(view); } size_t i = 0; __m128i running = __lsx_vreplgr2vr_b(0); __m128i v34 = __lsx_vreplgr2vr_b(34); __m128i v92 = __lsx_vreplgr2vr_b(92); __m128i v32 = __lsx_vreplgr2vr_b(32); for (; i + 15 < view.size(); i += 16) { __m128i word = __lsx_vld(reinterpret_cast(view.data() + i), 0); __m128i chunk = __lsx_vseq_b(word, v34); chunk = __lsx_vor_v(chunk, __lsx_vseq_b(word, v92)); chunk = __lsx_vor_v(chunk, __lsx_vslt_bu(word, v32)); running = __lsx_vor_v(running, chunk); } if (i < view.size()) { __m128i word = __lsx_vld( reinterpret_cast(view.data() + view.length() - 16), 0); __m128i chunk = __lsx_vseq_b(word, v34); chunk = __lsx_vor_v(chunk, __lsx_vseq_b(word, v92)); chunk = __lsx_vor_v(chunk, __lsx_vslt_bu(word, v32)); running = __lsx_vor_v(running, chunk); } return !__lsx_bz_v(running); } #elif SIMDJSON_EXPERIMENTAL_HAS_PPC64 simdjson_inline bool fast_needs_escaping(std::string_view view) { if (view.size() < 16) { return simple_needs_escaping(view); } size_t i = 0; __vector unsigned char running = vec_splats((unsigned char)0); __vector unsigned char v34 = vec_splats((unsigned char)34); __vector unsigned char v92 = vec_splats((unsigned char)92); __vector unsigned char v32 = vec_splats((unsigned char)32); for (; i + 15 < view.size(); i += 16) { __vector unsigned char word = vec_vsx_ld(0, reinterpret_cast(view.data() + i)); running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v34)); running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v92)); running = vec_or(running, (__vector unsigned char)vec_cmplt(word, v32)); } if (i < view.size()) { __vector unsigned char word = vec_vsx_ld( 0, reinterpret_cast(view.data() + view.length() - 16)); running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v34)); running = vec_or(running, (__vector unsigned char)vec_cmpeq(word, v92)); running = vec_or(running, (__vector unsigned char)vec_cmplt(word, v32)); } return !vec_all_eq(running, vec_splats((unsigned char)0)); } #else simdjson_inline bool fast_needs_escaping(std::string_view view) { return simple_needs_escaping(view); } #endif // Scalar fallback for finding next quotable character SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline size_t find_next_json_quotable_character_scalar(const std::string_view view, size_t location) noexcept { for (auto pos = view.begin() + location; pos != view.end(); ++pos) { if (json_quotable_character[static_cast(*pos)]) { return pos - view.begin(); } } return size_t(view.size()); } // SIMD-accelerated position finding that directly locates the first quotable // character, combining detection and position extraction in a single pass to // minimize redundant work. #if SIMDJSON_EXPERIMENTAL_HAS_NEON simdjson_inline size_t find_next_json_quotable_character(const std::string_view view, size_t location) noexcept { const size_t len = view.size(); const uint8_t *ptr = reinterpret_cast(view.data()) + location; size_t remaining = len - location; // SIMD constants for characters requiring escape uint8x16_t v34 = vdupq_n_u8(34); // '"' uint8x16_t v92 = vdupq_n_u8(92); // '\\' uint8x16_t v32 = vdupq_n_u8(32); // control char threshold while (remaining >= 16) { uint8x16_t word = vld1q_u8(ptr); // Check for quotable characters: '"', '\\', or control chars (< 32) uint8x16_t needs_escape = vceqq_u8(word, v34); needs_escape = vorrq_u8(needs_escape, vceqq_u8(word, v92)); needs_escape = vorrq_u8(needs_escape, vcltq_u8(word, v32)); const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(needs_escape), 4); const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0); if(mask != 0) { size_t offset = ptr - reinterpret_cast(view.data()); auto trailing_zero = trailing_zeroes(mask); return offset + (trailing_zero >> 2); } ptr += 16; remaining -= 16; } // Scalar fallback for remaining bytes size_t current = len - remaining; return find_next_json_quotable_character_scalar(view, current); } #elif SIMDJSON_EXPERIMENTAL_HAS_SSE2 simdjson_inline size_t find_next_json_quotable_character(const std::string_view view, size_t location) noexcept { const size_t len = view.size(); const uint8_t *ptr = reinterpret_cast(view.data()) + location; size_t remaining = len - location; // SIMD constants __m128i v34 = _mm_set1_epi8(34); // '"' __m128i v92 = _mm_set1_epi8(92); // '\\' __m128i v31 = _mm_set1_epi8(31); // for control char detection while (remaining >= 16) { __m128i word = _mm_loadu_si128(reinterpret_cast(ptr)); // Check for quotable characters __m128i needs_escape = _mm_cmpeq_epi8(word, v34); needs_escape = _mm_or_si128(needs_escape, _mm_cmpeq_epi8(word, v92)); needs_escape = _mm_or_si128( needs_escape, _mm_cmpeq_epi8(_mm_subs_epu8(word, v31), _mm_setzero_si128())); int mask = _mm_movemask_epi8(needs_escape); if (mask != 0) { // Found quotable character - use trailing zero count to find position size_t offset = ptr - reinterpret_cast(view.data()); return offset + trailing_zeroes(mask); } ptr += 16; remaining -= 16; } // Scalar fallback for remaining bytes size_t current = len - remaining; return find_next_json_quotable_character_scalar(view, current); } #elif SIMDJSON_EXPERIMENTAL_HAS_LSX simdjson_inline size_t find_next_json_quotable_character(const std::string_view view, size_t location) noexcept { const size_t len = view.size(); const uint8_t *ptr = reinterpret_cast(view.data()) + location; size_t remaining = len - location; //SIMD constants for characters requiring escape __m128i v34 = __lsx_vreplgr2vr_b(34); // '"' __m128i v92 = __lsx_vreplgr2vr_b(92); // '\\' __m128i v32 = __lsx_vreplgr2vr_b(32); // control char threshold while (remaining >= 16){ __m128i word = __lsx_vld(ptr, 0); //Check for the quotable characters: '"', '\\', or control char (<32) __m128i needs_escape = __lsx_vseq_b(word, v34); needs_escape = __lsx_vor_v(needs_escape, __lsx_vseq_b(word, v92)); needs_escape = __lsx_vor_v(needs_escape, __lsx_vslt_bu(word, v32)); if (!__lsx_bz_v(needs_escape)){ //Found quotable character - extract exact byte position uint64_t lo = __lsx_vpickve2gr_du(needs_escape,0); uint64_t hi = __lsx_vpickve2gr_du(needs_escape,1); size_t offset = ptr - reinterpret_cast(view.data()); if ( lo != 0) { return offset + trailing_zeroes(lo) / 8; } else { return offset + 8 + trailing_zeroes(hi) / 8; } } ptr += 16; remaining -= 16; } size_t current = len - remaining; return find_next_json_quotable_character_scalar(view, current); } #elif SIMDJSON_EXPERIMENTAL_HAS_RVV simdjson_inline size_t find_next_json_quotable_character(const std::string_view view, size_t location) noexcept { const size_t len = view.size(); const uint8_t *ptr = reinterpret_cast(view.data()) + location; size_t remaining = len - location; while (remaining > 0) { size_t vl = __riscv_vsetvl_e8m1(remaining); vuint8m1_t word = __riscv_vle8_v_u8m1(ptr, vl); // Check for quotable characters: '"', '\\', or control chars (< 32) vbool8_t needs_escape = __riscv_vmseq(word, (uint8_t)34, vl); needs_escape = __riscv_vmor(needs_escape, __riscv_vmseq(word, (uint8_t)92, vl), vl); needs_escape = __riscv_vmor(needs_escape, __riscv_vmsltu(word, (uint8_t)32, vl), vl); long first = __riscv_vfirst(needs_escape, vl); if (first >= 0) { size_t offset = ptr - reinterpret_cast(view.data()); return offset + first; } ptr += vl; remaining -= vl; } return len; } #elif SIMDJSON_EXPERIMENTAL_HAS_PPC64 simdjson_inline size_t find_next_json_quotable_character(const std::string_view view, size_t location) noexcept { const size_t len = view.size(); const uint8_t *ptr = reinterpret_cast(view.data()) + location; size_t remaining = len - location; // SIMD constants for characters requiring escape __vector unsigned char v34 = vec_splats((unsigned char)34); // '"' __vector unsigned char v92 = vec_splats((unsigned char)92); // '\\' __vector unsigned char v32 = vec_splats((unsigned char)32); // control char threshold // Bitmask for vec_vbpermq to extract one bit per byte const __vector unsigned char perm_mask = {0x78, 0x70, 0x68, 0x60, 0x58, 0x50, 0x48, 0x40, 0x38, 0x30, 0x28, 0x20, 0x18, 0x10, 0x08, 0x00}; while (remaining >= 16) { __vector unsigned char word = vec_vsx_ld(0, reinterpret_cast(ptr)); // Check for quotable characters: '"', '\\', or control chars (< 32) __vector unsigned char needs_escape = (__vector unsigned char)vec_cmpeq(word, v34); needs_escape = vec_or(needs_escape, (__vector unsigned char)vec_cmpeq(word, v92)); needs_escape = vec_or(needs_escape, (__vector unsigned char)vec_cmplt(word, v32)); __vector unsigned long long result = (__vector unsigned long long)vec_vbpermq(needs_escape, perm_mask); #ifdef __LITTLE_ENDIAN__ unsigned int mask = static_cast(result[1]); #else unsigned int mask = static_cast(result[0]); #endif if (mask != 0) { size_t offset = ptr - reinterpret_cast(view.data()); return offset + __builtin_ctz(mask); } ptr += 16; remaining -= 16; } // Scalar fallback for remaining bytes size_t current = len - remaining; return find_next_json_quotable_character_scalar(view, current); } #else SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline size_t find_next_json_quotable_character(const std::string_view view, size_t location) noexcept { return find_next_json_quotable_character_scalar(view, location); } #endif SIMDJSON_CONSTEXPR_LAMBDA static std::string_view control_chars[] = { "\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", "\\u0007", "\\b", "\\t", "\\n", "\\u000b", "\\f", "\\r", "\\u000e", "\\u000f", "\\u0010", "\\u0011", "\\u0012", "\\u0013", "\\u0014", "\\u0015", "\\u0016", "\\u0017", "\\u0018", "\\u0019", "\\u001a", "\\u001b", "\\u001c", "\\u001d", "\\u001e", "\\u001f"}; // All Unicode characters may be placed within the quotation marks, except for // the characters that MUST be escaped: quotation mark, reverse solidus, and the // control characters (U+0000 through U+001F). There are two-character sequence // escape representations of some popular characters: // \", \\, \b, \f, \n, \r, \t. SIMDJSON_CONSTEXPR_LAMBDA simdjson_inline void escape_json_char(char c, char *&out) { if (c == '"') { memcpy(out, "\\\"", 2); out += 2; } else if (c == '\\') { memcpy(out, "\\\\", 2); out += 2; } else { std::string_view v = control_chars[uint8_t(c)]; memcpy(out, v.data(), v.size()); out += v.size(); } } // Writes the escaped version of input to out, returning the number of bytes // written. Uses SIMD position finding to locate quotable characters efficiently. inline size_t write_string_escaped(const std::string_view input, char *out) { size_t mysize = input.size(); // Use SIMD position finder directly - it returns mysize if no escape needed size_t location = find_next_json_quotable_character(input, 0); if (location == mysize) { // Fast path: no escaping needed memcpy(out, input.data(), input.size()); return input.size(); } const char *const initout = out; memcpy(out, input.data(), location); out += location; escape_json_char(input[location], out); location += 1; while (location < mysize) { size_t newlocation = find_next_json_quotable_character(input, location); memcpy(out, input.data() + location, newlocation - location); out += newlocation - location; location = newlocation; if (location == mysize) { break; } escape_json_char(input[location], out); location += 1; } return out - initout; } simdjson_inline string_builder::string_builder(size_t initial_capacity) : buffer(new(std::nothrow) char[initial_capacity]), position(0), capacity(buffer.get() != nullptr ? initial_capacity : 0), is_valid(buffer.get() != nullptr) {} simdjson_inline bool string_builder::capacity_check(size_t upcoming_bytes) { // We use the convention that when is_valid is false, then the capacity and // the position are 0. // Most of the time, this function will return true. if (simdjson_likely(upcoming_bytes <= capacity - position)) { return true; } // check for overflow, most of the time there is no overflow if (simdjson_unlikely(position + upcoming_bytes < position)) { return false; } // We will rarely get here. grow_buffer((std::max)(capacity * 2, position + upcoming_bytes)); // If the buffer allocation failed, we set is_valid to false. return is_valid; } inline void string_builder::grow_buffer(size_t desired_capacity) { if (!is_valid) { return; } std::unique_ptr new_buffer(new (std::nothrow) char[desired_capacity]); if (new_buffer.get() == nullptr) { set_valid(false); return; } std::memcpy(new_buffer.get(), buffer.get(), position); buffer.swap(new_buffer); capacity = desired_capacity; } simdjson_inline void string_builder::set_valid(bool valid) noexcept { if (!valid) { is_valid = false; capacity = 0; position = 0; buffer.reset(); } else { is_valid = true; } } simdjson_inline size_t string_builder::size() const noexcept { return position; } simdjson_inline void string_builder::append(char c) noexcept { if (capacity_check(1)) { buffer.get()[position++] = c; } } simdjson_inline void string_builder::append_null() noexcept { constexpr char null_literal[] = "null"; constexpr size_t null_len = sizeof(null_literal) - 1; if (capacity_check(null_len)) { std::memcpy(buffer.get() + position, null_literal, null_len); position += null_len; } } simdjson_inline void string_builder::clear() noexcept { position = 0; // if it was invalid, we should try to repair it if (!is_valid) { capacity = 0; buffer.reset(); is_valid = true; } } namespace internal { static const char decimal_table[200] = { 0x30, 0x30, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, 0x30, 0x34, 0x30, 0x35, 0x30, 0x36, 0x30, 0x37, 0x30, 0x38, 0x30, 0x39, 0x31, 0x30, 0x31, 0x31, 0x31, 0x32, 0x31, 0x33, 0x31, 0x34, 0x31, 0x35, 0x31, 0x36, 0x31, 0x37, 0x31, 0x38, 0x31, 0x39, 0x32, 0x30, 0x32, 0x31, 0x32, 0x32, 0x32, 0x33, 0x32, 0x34, 0x32, 0x35, 0x32, 0x36, 0x32, 0x37, 0x32, 0x38, 0x32, 0x39, 0x33, 0x30, 0x33, 0x31, 0x33, 0x32, 0x33, 0x33, 0x33, 0x34, 0x33, 0x35, 0x33, 0x36, 0x33, 0x37, 0x33, 0x38, 0x33, 0x39, 0x34, 0x30, 0x34, 0x31, 0x34, 0x32, 0x34, 0x33, 0x34, 0x34, 0x34, 0x35, 0x34, 0x36, 0x34, 0x37, 0x34, 0x38, 0x34, 0x39, 0x35, 0x30, 0x35, 0x31, 0x35, 0x32, 0x35, 0x33, 0x35, 0x34, 0x35, 0x35, 0x35, 0x36, 0x35, 0x37, 0x35, 0x38, 0x35, 0x39, 0x36, 0x30, 0x36, 0x31, 0x36, 0x32, 0x36, 0x33, 0x36, 0x34, 0x36, 0x35, 0x36, 0x36, 0x36, 0x37, 0x36, 0x38, 0x36, 0x39, 0x37, 0x30, 0x37, 0x31, 0x37, 0x32, 0x37, 0x33, 0x37, 0x34, 0x37, 0x35, 0x37, 0x36, 0x37, 0x37, 0x37, 0x38, 0x37, 0x39, 0x38, 0x30, 0x38, 0x31, 0x38, 0x32, 0x38, 0x33, 0x38, 0x34, 0x38, 0x35, 0x38, 0x36, 0x38, 0x37, 0x38, 0x38, 0x38, 0x39, 0x39, 0x30, 0x39, 0x31, 0x39, 0x32, 0x39, 0x33, 0x39, 0x34, 0x39, 0x35, 0x39, 0x36, 0x39, 0x37, 0x39, 0x38, 0x39, 0x39, }; // Forward unsigned-int writer (cascade-on-magnitude, no upfront digit_count). // Built from a non-recursive DAG of always_inline helpers — gcc and MSVC // refuse to inline recursive `always_inline`/`__forceinline` functions. // Caller must guarantee at least 20 bytes available at p. All helpers // return pointer past the last digit written. // Caller guarantees v < 100. Writes 1-2 digits. simdjson_really_inline char* write_lt100(char* p, uint64_t v) noexcept { if (v < 10) { *p++ = char('0' + v); return p; } std::memcpy(p, &decimal_table[v * 2], 2); return p + 2; } // Caller guarantees v < 10000. Writes 1-4 digits. simdjson_really_inline char* write_lt10000(char* p, uint64_t v) noexcept { if (v < 100) return write_lt100(p, v); uint64_t hi = v / 100, lo = v % 100; if (v < 1000) { *p++ = char('0' + hi); } else { std::memcpy(p, &decimal_table[hi * 2], 2); p += 2; } std::memcpy(p, &decimal_table[lo * 2], 2); return p + 2; } // Caller guarantees v < 10000. Always writes exactly 4 digits. simdjson_really_inline void write_4_digits(char* p, uint64_t v) noexcept { uint64_t hi = v / 100, lo = v % 100; std::memcpy(p, &decimal_table[hi * 2], 2); std::memcpy(p + 2, &decimal_table[lo * 2], 2); } // Caller guarantees v < 10^8. Writes 1-8 digits. simdjson_really_inline char* write_lt1e8(char* p, uint64_t v) noexcept { if (v < 10000) return write_lt10000(p, v); uint64_t hi = v / 10000, lo = v % 10000; p = write_lt10000(p, hi); write_4_digits(p, lo); return p + 4; } simdjson_really_inline char* write_uint_jeaiii(char* p, uint64_t v) noexcept { if (v < 10000ULL) return write_lt10000(p, v); if (v < 100000000ULL) { // 5-8 digits uint64_t hi = v / 10000, lo = v % 10000; p = write_lt10000(p, hi); write_4_digits(p, lo); return p + 4; } if (v < 10000000000000000ULL) { // 9-16 digits uint64_t hi = v / 100000000ULL, lo = v % 100000000ULL; p = write_lt1e8(p, hi); uint64_t lo_hi = lo / 10000, lo_lo = lo % 10000; write_4_digits(p, lo_hi); write_4_digits(p + 4, lo_lo); return p + 8; } // 17-20 digits uint64_t hi = v / 10000000000000000ULL, lo = v % 10000000000000000ULL; p = write_lt10000(p, hi); uint64_t lo_a = lo / 100000000ULL, lo_b = lo % 100000000ULL; uint64_t lo_a_hi = lo_a / 10000, lo_a_lo = lo_a % 10000; uint64_t lo_b_hi = lo_b / 10000, lo_b_lo = lo_b % 10000; write_4_digits(p, lo_a_hi); write_4_digits(p + 4, lo_a_lo); write_4_digits(p + 8, lo_b_hi); write_4_digits(p + 12, lo_b_lo); return p + 16; } } // namespace internal template simdjson_inline void string_builder::append(number_type v) noexcept { static_assert(std::is_same::value || std::is_integral::value || std::is_floating_point::value, "Unsupported number type"); // If C++17 is available, we can 'if constexpr' here. SIMDJSON_IF_CONSTEXPR(std::is_same::value) { if (v) { constexpr char true_literal[] = "true"; constexpr size_t true_len = sizeof(true_literal) - 1; if (capacity_check(true_len)) { std::memcpy(buffer.get() + position, true_literal, true_len); position += true_len; } } else { constexpr char false_literal[] = "false"; constexpr size_t false_len = sizeof(false_literal) - 1; if (capacity_check(false_len)) { std::memcpy(buffer.get() + position, false_literal, false_len); position += false_len; } } } else SIMDJSON_IF_CONSTEXPR(std::is_unsigned::value) { constexpr size_t max_number_size = 20; if (capacity_check(max_number_size)) { using unsigned_type = typename std::make_unsigned::type; char* end = internal::write_uint_jeaiii( buffer.get() + position, static_cast(static_cast(v))); position = end - buffer.get(); } } else SIMDJSON_IF_CONSTEXPR(std::is_integral::value) { // 19 digits (max abs value of int64_t) + optional minus sign. constexpr size_t max_number_size = 20; if (capacity_check(max_number_size)) { using unsigned_type = typename std::make_unsigned::type; bool negative = v < 0; // 0 - pv (rather than -pv) avoids an MSVC unary-minus warning. unsigned_type pv = negative ? unsigned_type(0) - static_cast(v) : static_cast(v); // Branchless: always write '-', advance only if negative. buffer.get()[position] = '-'; position += negative; char* end = internal::write_uint_jeaiii( buffer.get() + position, static_cast(pv)); position = end - buffer.get(); } } else SIMDJSON_IF_CONSTEXPR(std::is_floating_point::value) { constexpr size_t max_number_size = 24; if (capacity_check(max_number_size)) { #if SIMDJSON_ENABLE_NAN_INF // Check if the input might be NaN or infinity if (simdjson_unlikely(!std::isfinite(v))) { if (std::isnan(v)) { constexpr char nan_literal[] = "NaN"; constexpr size_t nan_len = sizeof(nan_literal) - 1; std::memcpy(buffer.get() + position, nan_literal, nan_len); position += nan_len; } else { constexpr char inf_literal[] = "Infinity"; constexpr size_t inf_len = sizeof(inf_literal) - 1; if (v < 0) { buffer.get()[position] = '-'; ++position; } std::memcpy(buffer.get() + position, inf_literal, inf_len); position += inf_len; } return; } #endif // We could specialize for float. char *end = simdjson::internal::to_chars(buffer.get() + position, nullptr, double(v)); position = end - buffer.get(); } } } simdjson_inline void string_builder::escape_and_append(std::string_view input) noexcept { // escaping might turn a control character into \x00xx so 6 characters. // Guard against size_t overflow in the multiplication below. if (input.size() > (std::numeric_limits::max)() / 6) { set_valid(false); return; } if (capacity_check(6 * input.size())) { position += write_string_escaped(input, buffer.get() + position); } } simdjson_inline void string_builder::escape_and_append_with_quotes(std::string_view input) noexcept { // escaping might turn a control character into \x00xx so 6 characters. // Guard against size_t overflow in the arithmetic below. if (input.size() > ((std::numeric_limits::max)() - 2) / 6) { set_valid(false); return; } if (capacity_check(2 + 6 * input.size())) { buffer.get()[position++] = '"'; position += write_string_escaped(input, buffer.get() + position); buffer.get()[position++] = '"'; } } simdjson_inline void string_builder::escape_and_append_with_quotes(char input) noexcept { // escaping might turn a control character into \x00xx so 6 characters. if (capacity_check(2 + 6 * 1)) { buffer.get()[position++] = '"'; std::string_view cinput(&input, 1); position += write_string_escaped(cinput, buffer.get() + position); buffer.get()[position++] = '"'; } } simdjson_inline void string_builder::escape_and_append_with_quotes(const char *input) noexcept { std::string_view cinput(input); escape_and_append_with_quotes(cinput); } #if SIMDJSON_SUPPORTS_CONCEPTS template simdjson_inline void string_builder::escape_and_append_with_quotes() noexcept { escape_and_append_with_quotes(constevalutil::string_constant::value); } #endif simdjson_inline void string_builder::append_raw(const char *c) noexcept { // char_traits::length is constexpr; lets the compiler fold the length // when called with a pointer to a compile-time-constant string. size_t len = std::char_traits::length(c); append_raw(c, len); } simdjson_inline void string_builder::append_raw(std::string_view input) noexcept { if (capacity_check(input.size())) { std::memcpy(buffer.get() + position, input.data(), input.size()); position += input.size(); } } simdjson_inline void string_builder::append_raw(const char *str, size_t len) noexcept { if (capacity_check(len)) { std::memcpy(buffer.get() + position, str, len); position += len; } } template simdjson_inline void string_builder::append_raw_n(const char *str) noexcept { if (capacity_check(N)) { std::memcpy(buffer.get() + position, str, N); position += N; } } #if SIMDJSON_SUPPORTS_CONCEPTS // Support for optional types (std::optional, etc.) template requires(!require_custom_serialization) simdjson_inline void string_builder::append(const T &opt) { if (opt) { append(*opt); } else { append_null(); } } template requires(require_custom_serialization) simdjson_inline void string_builder::append(T &&val) { serialize(*this, std::forward(val)); } template requires(std::is_convertible::value || std::is_same::value) simdjson_inline void string_builder::append(const T &value) { escape_and_append_with_quotes(value); } #endif #if SIMDJSON_SUPPORTS_RANGES && SIMDJSON_SUPPORTS_CONCEPTS // Support for range-based appending (std::ranges::view, etc.) template requires(!std::is_convertible::value && !concepts::optional_type && !require_custom_serialization) simdjson_inline void string_builder::append(const R &range) noexcept { auto it = std::ranges::begin(range); auto end = std::ranges::end(range); if constexpr (concepts::is_pair>) { start_object(); if (it == end) { end_object(); return; // Handle empty range } // Append first item without leading comma append_key_value(it->first, it->second); ++it; // Append remaining items with preceding commas for (; it != end; ++it) { append_comma(); append_key_value(it->first, it->second); } end_object(); } else { start_array(); if (it == end) { end_array(); return; // Handle empty range } // Append first item without leading comma append(*it); ++it; // Append remaining items with preceding commas for (; it != end; ++it) { append_comma(); append(*it); } end_array(); } } #endif #if SIMDJSON_EXCEPTIONS simdjson_inline string_builder::operator std::string() const noexcept(false) { return std::string(operator std::string_view()); } simdjson_inline string_builder::operator std::string_view() const noexcept(false) simdjson_lifetime_bound { return view(); } #endif simdjson_inline simdjson_result string_builder::view() const noexcept { if (!is_valid) { return simdjson::OUT_OF_CAPACITY; } return std::string_view(buffer.get(), position); } simdjson_inline simdjson_result string_builder::c_str() noexcept { if (capacity_check(1)) { buffer.get()[position] = '\0'; return buffer.get(); } return simdjson::OUT_OF_CAPACITY; } simdjson_inline bool string_builder::validate_unicode() const noexcept { return simdjson::validate_utf8(buffer.get(), position); } simdjson_inline void string_builder::start_object() noexcept { if (capacity_check(1)) { buffer.get()[position++] = '{'; } } simdjson_inline void string_builder::end_object() noexcept { if (capacity_check(1)) { buffer.get()[position++] = '}'; } } simdjson_inline void string_builder::start_array() noexcept { if (capacity_check(1)) { buffer.get()[position++] = '['; } } simdjson_inline void string_builder::end_array() noexcept { if (capacity_check(1)) { buffer.get()[position++] = ']'; } } simdjson_inline void string_builder::append_comma() noexcept { if (capacity_check(1)) { buffer.get()[position++] = ','; } } simdjson_inline void string_builder::append_colon() noexcept { if (capacity_check(1)) { buffer.get()[position++] = ':'; } } template simdjson_inline void string_builder::append_key_value(key_type key, value_type value) noexcept { static_assert(std::is_same::value || std::is_convertible::value, "Unsupported key type"); escape_and_append_with_quotes(key); append_colon(); SIMDJSON_IF_CONSTEXPR(std::is_same::value) { append_null(); } else SIMDJSON_IF_CONSTEXPR(std::is_same::value) { escape_and_append_with_quotes(value); } else SIMDJSON_IF_CONSTEXPR( std::is_convertible::value) { escape_and_append_with_quotes(value); } else SIMDJSON_IF_CONSTEXPR(std::is_same::value) { escape_and_append_with_quotes(value); } else { append(value); } } #if SIMDJSON_SUPPORTS_CONCEPTS template simdjson_inline void string_builder::append_key_value(value_type value) noexcept { escape_and_append_with_quotes(); append_colon(); SIMDJSON_IF_CONSTEXPR(std::is_same::value) { append_null(); } else SIMDJSON_IF_CONSTEXPR(std::is_same::value) { escape_and_append_with_quotes(value); } else SIMDJSON_IF_CONSTEXPR( std::is_convertible::value) { escape_and_append_with_quotes(value); } else SIMDJSON_IF_CONSTEXPR(std::is_same::value) { escape_and_append_with_quotes(value); } else { append(value); } } #endif } // namespace builder } // namespace SIMDJSON_IMPLEMENTATION } // namespace simdjson #endif // SIMDJSON_GENERIC_STRING_BUILDER_INL_H