/* auto-generated on Sun Oct 6 13:23:03 DST 2019. Do not edit! */ #include "simdjson.h" /* used for http://dmalloc.com/ Dmalloc - Debug Malloc Library */ #ifdef DMALLOC #include "dmalloc.h" #endif /* begin file src/simdjson.cpp */ #include namespace simdjson { const std::map error_strings = { {SUCCESS, "No errors"}, {CAPACITY, "This ParsedJson can't support a document that big"}, {MEMALLOC, "Error allocating memory, we're most likely out of memory"}, {TAPE_ERROR, "Something went wrong while writing to the tape"}, {STRING_ERROR, "Problem while parsing a string"}, {T_ATOM_ERROR, "Problem while parsing an atom starting with the letter 't'"}, {F_ATOM_ERROR, "Problem while parsing an atom starting with the letter 'f'"}, {N_ATOM_ERROR, "Problem while parsing an atom starting with the letter 'n'"}, {NUMBER_ERROR, "Problem while parsing a number"}, {UTF8_ERROR, "The input is not valid UTF-8"}, {UNITIALIZED, "Unitialized"}, {EMPTY, "Empty"}, {UNESCAPED_CHARS, "Within strings, some characters must be escaped, we " "found unescaped characters"}, {UNCLOSED_STRING, "A string is opened, but never closed."}, {UNEXPECTED_ERROR, "Unexpected error, consider reporting this problem as " "you may have found a bug in simdjson"}, }; // string returned when the error code is not recognized const std::string unexpected_error_msg {"Unexpected error"}; // returns a string matching the error code const std::string &error_message(const int error_code) { auto keyvalue = error_strings.find(error_code); if(keyvalue == error_strings.end()) { return unexpected_error_msg; } return keyvalue->second; } } // namespace simdjson /* end file src/simdjson.cpp */ /* begin file src/jsonioutil.cpp */ #include #include namespace simdjson { char *allocate_padded_buffer(size_t length) { // we could do a simple malloc // return (char *) malloc(length + SIMDJSON_PADDING); // However, we might as well align to cache lines... size_t totalpaddedlength = length + SIMDJSON_PADDING; char *padded_buffer = aligned_malloc_char(64, totalpaddedlength); return padded_buffer; } padded_string get_corpus(const std::string &filename) { std::FILE *fp = std::fopen(filename.c_str(), "rb"); if (fp != nullptr) { std::fseek(fp, 0, SEEK_END); size_t len = std::ftell(fp); padded_string s(len); if (s.data() == nullptr) { std::fclose(fp); throw std::runtime_error("could not allocate memory"); } std::rewind(fp); size_t readb = std::fread(s.data(), 1, len, fp); std::fclose(fp); if (readb != len) { throw std::runtime_error("could not read the data"); } return s; } throw std::runtime_error("could not load corpus"); } } // namespace simdjson /* end file src/jsonioutil.cpp */ /* begin file src/jsonminifier.cpp */ #include #ifndef __AVX2__ namespace simdjson { static uint8_t jump_table[256 * 3] = { 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, }; size_t json_minify(const unsigned char *bytes, size_t how_many, unsigned char *out) { size_t i = 0, pos = 0; uint8_t quote = 0; uint8_t nonescape = 1; while (i < how_many) { unsigned char c = bytes[i]; uint8_t *meta = jump_table + 3 * c; quote = quote ^ (meta[0] & nonescape); out[pos] = c; pos += meta[2] | quote; i += 1; nonescape = (~nonescape) | (meta[1]); } return pos; } } // namespace simdjson #else #include namespace simdjson { // some intrinsics are missing under GCC? #ifndef __clang__ #ifndef _MSC_VER static __m256i inline _mm256_loadu2_m128i(__m128i const *__addr_hi, __m128i const *__addr_lo) { __m256i __v256 = _mm256_castsi128_si256(_mm_loadu_si128(__addr_lo)); return _mm256_insertf128_si256(__v256, _mm_loadu_si128(__addr_hi), 1); } static inline void _mm256_storeu2_m128i(__m128i *__addr_hi, __m128i *__addr_lo, __m256i __a) { __m128i __v128; __v128 = _mm256_castsi256_si128(__a); _mm_storeu_si128(__addr_lo, __v128); __v128 = _mm256_extractf128_si256(__a, 1); _mm_storeu_si128(__addr_hi, __v128); } #endif #endif // a straightforward comparison of a mask against input. static uint64_t cmp_mask_against_input_mini(__m256i input_lo, __m256i input_hi, __m256i mask) { __m256i cmp_res_0 = _mm256_cmpeq_epi8(input_lo, mask); uint64_t res_0 = static_cast(_mm256_movemask_epi8(cmp_res_0)); __m256i cmp_res_1 = _mm256_cmpeq_epi8(input_hi, mask); uint64_t res_1 = _mm256_movemask_epi8(cmp_res_1); return res_0 | (res_1 << 32); } // take input from buf and remove useless whitespace, input and output can be // the same, result is null terminated, return the string length (minus the null // termination) size_t json_minify(const uint8_t *buf, size_t len, uint8_t *out) { // Useful constant masks const uint64_t even_bits = 0x5555555555555555ULL; const uint64_t odd_bits = ~even_bits; uint8_t *initout(out); uint64_t prev_iter_ends_odd_backslash = 0ULL; // either 0 or 1, but a 64-bit value uint64_t prev_iter_inside_quote = 0ULL; // either all zeros or all ones size_t idx = 0; if (len >= 64) { size_t avx_len = len - 63; for (; idx < avx_len; idx += 64) { __m256i input_lo = _mm256_loadu_si256(reinterpret_cast(buf + idx + 0)); __m256i input_hi = _mm256_loadu_si256(reinterpret_cast(buf + idx + 32)); uint64_t bs_bits = cmp_mask_against_input_mini(input_lo, input_hi, _mm256_set1_epi8('\\')); uint64_t start_edges = bs_bits & ~(bs_bits << 1); uint64_t even_start_mask = even_bits ^ prev_iter_ends_odd_backslash; uint64_t even_starts = start_edges & even_start_mask; uint64_t odd_starts = start_edges & ~even_start_mask; uint64_t even_carries = bs_bits + even_starts; uint64_t odd_carries; bool iter_ends_odd_backslash = add_overflow(bs_bits, odd_starts, &odd_carries); odd_carries |= prev_iter_ends_odd_backslash; prev_iter_ends_odd_backslash = iter_ends_odd_backslash ? 0x1ULL : 0x0ULL; uint64_t even_carry_ends = even_carries & ~bs_bits; uint64_t odd_carry_ends = odd_carries & ~bs_bits; uint64_t even_start_odd_end = even_carry_ends & odd_bits; uint64_t odd_start_even_end = odd_carry_ends & even_bits; uint64_t odd_ends = even_start_odd_end | odd_start_even_end; uint64_t quote_bits = cmp_mask_against_input_mini(input_lo, input_hi, _mm256_set1_epi8('"')); quote_bits = quote_bits & ~odd_ends; uint64_t quote_mask = _mm_cvtsi128_si64(_mm_clmulepi64_si128( _mm_set_epi64x(0ULL, quote_bits), _mm_set1_epi8(0xFF), 0)); quote_mask ^= prev_iter_inside_quote; prev_iter_inside_quote = static_cast( static_cast(quote_mask) >> 63); // might be undefined behavior, should be fully defined in C++20, // ok according to John Regher from Utah University const __m256i low_nibble_mask = _mm256_setr_epi8( // 0 9 a b c d 16, 0, 0, 0, 0, 0, 0, 0, 0, 8, 12, 1, 2, 9, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 8, 12, 1, 2, 9, 0, 0); const __m256i high_nibble_mask = _mm256_setr_epi8( // 0 2 3 5 7 8, 0, 18, 4, 0, 1, 0, 1, 0, 0, 0, 3, 2, 1, 0, 0, 8, 0, 18, 4, 0, 1, 0, 1, 0, 0, 0, 3, 2, 1, 0, 0); __m256i whitespace_shufti_mask = _mm256_set1_epi8(0x18); __m256i v_lo = _mm256_and_si256( _mm256_shuffle_epi8(low_nibble_mask, input_lo), _mm256_shuffle_epi8(high_nibble_mask, _mm256_and_si256(_mm256_srli_epi32(input_lo, 4), _mm256_set1_epi8(0x7f)))); __m256i v_hi = _mm256_and_si256( _mm256_shuffle_epi8(low_nibble_mask, input_hi), _mm256_shuffle_epi8(high_nibble_mask, _mm256_and_si256(_mm256_srli_epi32(input_hi, 4), _mm256_set1_epi8(0x7f)))); __m256i tmp_ws_lo = _mm256_cmpeq_epi8( _mm256_and_si256(v_lo, whitespace_shufti_mask), _mm256_set1_epi8(0)); __m256i tmp_ws_hi = _mm256_cmpeq_epi8( _mm256_and_si256(v_hi, whitespace_shufti_mask), _mm256_set1_epi8(0)); uint64_t ws_res_0 = static_cast(_mm256_movemask_epi8(tmp_ws_lo)); uint64_t ws_res_1 = _mm256_movemask_epi8(tmp_ws_hi); uint64_t whitespace = ~(ws_res_0 | (ws_res_1 << 32)); whitespace &= ~quote_mask; int mask1 = whitespace & 0xFFFF; int mask2 = (whitespace >> 16) & 0xFFFF; int mask3 = (whitespace >> 32) & 0xFFFF; int mask4 = (whitespace >> 48) & 0xFFFF; int pop1 = hamming((~whitespace) & 0xFFFF); int pop2 = hamming((~whitespace) & UINT64_C(0xFFFFFFFF)); int pop3 = hamming((~whitespace) & UINT64_C(0xFFFFFFFFFFFF)); int pop4 = hamming((~whitespace)); __m256i vmask1 = _mm256_loadu2_m128i( reinterpret_cast(mask128_epi8) + (mask2 & 0x7FFF), reinterpret_cast(mask128_epi8) + (mask1 & 0x7FFF)); __m256i vmask2 = _mm256_loadu2_m128i( reinterpret_cast(mask128_epi8) + (mask4 & 0x7FFF), reinterpret_cast(mask128_epi8) + (mask3 & 0x7FFF)); __m256i result1 = _mm256_shuffle_epi8(input_lo, vmask1); __m256i result2 = _mm256_shuffle_epi8(input_hi, vmask2); _mm256_storeu2_m128i(reinterpret_cast<__m128i *>(out + pop1), reinterpret_cast<__m128i *>(out), result1); _mm256_storeu2_m128i(reinterpret_cast<__m128i *>(out + pop3), reinterpret_cast<__m128i *>(out + pop2), result2); out += pop4; } } // we finish off the job... copying and pasting the code is not ideal here, // but it gets the job done. if (idx < len) { uint8_t buffer[64]; memset(buffer, 0, 64); memcpy(buffer, buf + idx, len - idx); __m256i input_lo = _mm256_loadu_si256(reinterpret_cast(buffer)); __m256i input_hi = _mm256_loadu_si256(reinterpret_cast(buffer + 32)); uint64_t bs_bits = cmp_mask_against_input_mini(input_lo, input_hi, _mm256_set1_epi8('\\')); uint64_t start_edges = bs_bits & ~(bs_bits << 1); uint64_t even_start_mask = even_bits ^ prev_iter_ends_odd_backslash; uint64_t even_starts = start_edges & even_start_mask; uint64_t odd_starts = start_edges & ~even_start_mask; uint64_t even_carries = bs_bits + even_starts; uint64_t odd_carries; // bool iter_ends_odd_backslash = add_overflow(bs_bits, odd_starts, &odd_carries); odd_carries |= prev_iter_ends_odd_backslash; // prev_iter_ends_odd_backslash = iter_ends_odd_backslash ? 0x1ULL : 0x0ULL; // // we never use it uint64_t even_carry_ends = even_carries & ~bs_bits; uint64_t odd_carry_ends = odd_carries & ~bs_bits; uint64_t even_start_odd_end = even_carry_ends & odd_bits; uint64_t odd_start_even_end = odd_carry_ends & even_bits; uint64_t odd_ends = even_start_odd_end | odd_start_even_end; uint64_t quote_bits = cmp_mask_against_input_mini(input_lo, input_hi, _mm256_set1_epi8('"')); quote_bits = quote_bits & ~odd_ends; uint64_t quote_mask = _mm_cvtsi128_si64(_mm_clmulepi64_si128( _mm_set_epi64x(0ULL, quote_bits), _mm_set1_epi8(0xFF), 0)); quote_mask ^= prev_iter_inside_quote; // prev_iter_inside_quote = (uint64_t)((int64_t)quote_mask >> 63);// we // don't need this anymore __m256i mask_20 = _mm256_set1_epi8(0x20); // c==32 __m256i mask_70 = _mm256_set1_epi8(0x70); // adding 0x70 does not check low 4-bits // but moves any value >= 16 above 128 __m256i lut_cntrl = _mm256_setr_epi8( 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00); __m256i tmp_ws_lo = _mm256_or_si256( _mm256_cmpeq_epi8(mask_20, input_lo), _mm256_shuffle_epi8(lut_cntrl, _mm256_adds_epu8(mask_70, input_lo))); __m256i tmp_ws_hi = _mm256_or_si256( _mm256_cmpeq_epi8(mask_20, input_hi), _mm256_shuffle_epi8(lut_cntrl, _mm256_adds_epu8(mask_70, input_hi))); uint64_t ws_res_0 = static_cast(_mm256_movemask_epi8(tmp_ws_lo)); uint64_t ws_res_1 = _mm256_movemask_epi8(tmp_ws_hi); uint64_t whitespace = (ws_res_0 | (ws_res_1 << 32)); whitespace &= ~quote_mask; if (len - idx < 64) { whitespace |= UINT64_C(0xFFFFFFFFFFFFFFFF) << (len - idx); } int mask1 = whitespace & 0xFFFF; int mask2 = (whitespace >> 16) & 0xFFFF; int mask3 = (whitespace >> 32) & 0xFFFF; int mask4 = (whitespace >> 48) & 0xFFFF; int pop1 = hamming((~whitespace) & 0xFFFF); int pop2 = hamming((~whitespace) & UINT64_C(0xFFFFFFFF)); int pop3 = hamming((~whitespace) & UINT64_C(0xFFFFFFFFFFFF)); int pop4 = hamming((~whitespace)); __m256i vmask1 = _mm256_loadu2_m128i( reinterpret_cast(mask128_epi8) + (mask2 & 0x7FFF), reinterpret_cast(mask128_epi8) + (mask1 & 0x7FFF)); __m256i vmask2 = _mm256_loadu2_m128i( reinterpret_cast(mask128_epi8) + (mask4 & 0x7FFF), reinterpret_cast(mask128_epi8) + (mask3 & 0x7FFF)); __m256i result1 = _mm256_shuffle_epi8(input_lo, vmask1); __m256i result2 = _mm256_shuffle_epi8(input_hi, vmask2); _mm256_storeu2_m128i(reinterpret_cast<__m128i *>(buffer + pop1), reinterpret_cast<__m128i *>(buffer), result1); _mm256_storeu2_m128i(reinterpret_cast<__m128i *>(buffer + pop3), reinterpret_cast<__m128i *>(buffer + pop2), result2); memcpy(out, buffer, pop4); out += pop4; } *out = '\0'; // NULL termination return out - initout; } } // namespace simdjson #endif /* end file src/jsonminifier.cpp */ /* begin file src/jsonparser.cpp */ #include namespace simdjson { // The function that users are expected to call is json_parse. // We have more than one such function because we want to support several // instruction sets. // function pointer type for json_parse using json_parse_functype = int(const uint8_t *buf, size_t len, ParsedJson &pj, bool realloc); // Pointer that holds the json_parse implementation corresponding to the // available SIMD instruction set extern std::atomic json_parse_ptr; int json_parse(const uint8_t *buf, size_t len, ParsedJson &pj, bool realloc) { return json_parse_ptr.load(std::memory_order_relaxed)(buf, len, pj, realloc); } int json_parse(const char *buf, size_t len, ParsedJson &pj, bool realloc) { return json_parse_ptr.load(std::memory_order_relaxed)(reinterpret_cast(buf), len, pj, realloc); } Architecture find_best_supported_implementation() { constexpr uint32_t haswell_flags = instruction_set::AVX2 | instruction_set::PCLMULQDQ | instruction_set::BMI1 | instruction_set::BMI2; constexpr uint32_t westmere_flags = instruction_set::SSE42 | instruction_set::PCLMULQDQ; uint32_t supports = detect_supported_architectures(); // Order from best to worst (within architecture) if ((haswell_flags & supports) == haswell_flags) return Architecture::HASWELL; if ((westmere_flags & supports) == westmere_flags) return Architecture::WESTMERE; if (instruction_set::NEON) return Architecture::ARM64; return Architecture::NONE; } // Responsible to select the best json_parse implementation int json_parse_dispatch(const uint8_t *buf, size_t len, ParsedJson &pj, bool realloc) { Architecture best_implementation = find_best_supported_implementation(); // Selecting the best implementation switch (best_implementation) { #ifdef IS_X86_64 case Architecture::HASWELL: json_parse_ptr.store(&json_parse_implementation, std::memory_order_relaxed); break; case Architecture::WESTMERE: json_parse_ptr.store(&json_parse_implementation, std::memory_order_relaxed); break; #endif #ifdef IS_ARM64 case Architecture::ARM64: json_parse_ptr.store(&json_parse_implementation, std::memory_order_relaxed); break; #endif default: std::cerr << "The processor is not supported by simdjson." << std::endl; return simdjson::UNEXPECTED_ERROR; } return json_parse_ptr.load(std::memory_order_relaxed)(buf, len, pj, realloc); } std::atomic json_parse_ptr = &json_parse_dispatch; WARN_UNUSED ParsedJson build_parsed_json(const uint8_t *buf, size_t len, bool realloc) { ParsedJson pj; bool ok = pj.allocate_capacity(len); if (ok) { json_parse(buf, len, pj, realloc); } else { std::cerr << "failure during memory allocation " << std::endl; } return pj; } } // namespace simdjson /* end file src/jsonparser.cpp */ /* begin file src/arm64/simd_input.h */ #ifndef SIMDJSON_ARM64_SIMD_INPUT_H #define SIMDJSON_ARM64_SIMD_INPUT_H #ifdef IS_ARM64 namespace simdjson::arm64 { really_inline uint16_t neon_movemask(uint8x16_t input) { const uint8x16_t bit_mask = {0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80}; uint8x16_t minput = vandq_u8(input, bit_mask); uint8x16_t tmp = vpaddq_u8(minput, minput); tmp = vpaddq_u8(tmp, tmp); tmp = vpaddq_u8(tmp, tmp); return vgetq_lane_u16(vreinterpretq_u16_u8(tmp), 0); } really_inline uint64_t neon_movemask_bulk(uint8x16_t p0, uint8x16_t p1, uint8x16_t p2, uint8x16_t p3) { const uint8x16_t bit_mask = {0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80}; uint8x16_t t0 = vandq_u8(p0, bit_mask); uint8x16_t t1 = vandq_u8(p1, bit_mask); uint8x16_t t2 = vandq_u8(p2, bit_mask); uint8x16_t t3 = vandq_u8(p3, bit_mask); uint8x16_t sum0 = vpaddq_u8(t0, t1); uint8x16_t sum1 = vpaddq_u8(t2, t3); sum0 = vpaddq_u8(sum0, sum1); sum0 = vpaddq_u8(sum0, sum0); return vgetq_lane_u64(vreinterpretq_u64_u8(sum0), 0); } struct simd_input { const uint8x16_t chunks[4]; really_inline simd_input() : chunks{uint8x16_t(), uint8x16_t(), uint8x16_t(), uint8x16_t() } {} really_inline simd_input(const uint8x16_t chunk0, const uint8x16_t chunk1, const uint8x16_t chunk2, const uint8x16_t chunk3) : chunks{chunk0, chunk1, chunk2, chunk3 } {} really_inline simd_input(const uint8_t *ptr) : chunks{ vld1q_u8(ptr + 0*16), vld1q_u8(ptr + 1*16), vld1q_u8(ptr + 2*16), vld1q_u8(ptr + 3*16) } {} template really_inline void each(F const& each_chunk) const { each_chunk(this->chunks[0]); each_chunk(this->chunks[1]); each_chunk(this->chunks[2]); each_chunk(this->chunks[3]); } template really_inline simd_input map(F const& map_chunk) const { return simd_input( map_chunk(this->chunks[0]), map_chunk(this->chunks[1]), map_chunk(this->chunks[2]), map_chunk(this->chunks[3]) ); } template really_inline simd_input map(simd_input b, F const& map_chunk) const { return simd_input( map_chunk(this->chunks[0], b.chunks[0]), map_chunk(this->chunks[1], b.chunks[1]), map_chunk(this->chunks[2], b.chunks[2]), map_chunk(this->chunks[3], b.chunks[3]) ); } template really_inline uint8x16_t reduce(F const& reduce_pair) const { uint8x16_t r01 = reduce_pair(this->chunks[0], this->chunks[1]); uint8x16_t r23 = reduce_pair(this->chunks[2], this->chunks[3]); return reduce_pair(r01, r23); } really_inline uint64_t to_bitmask() const { return neon_movemask_bulk(this->chunks[0], this->chunks[1], this->chunks[2], this->chunks[3]); } really_inline simd_input bit_or(const uint8_t m) const { const uint8x16_t mask = vmovq_n_u8(m); return this->map( [&](auto a) { return vorrq_u8(a, mask); }); } really_inline uint64_t eq(const uint8_t m) const { const uint8x16_t mask = vmovq_n_u8(m); return this->map( [&](auto a) { return vceqq_u8(a, mask); }).to_bitmask(); } really_inline uint64_t lteq(const uint8_t m) const { const uint8x16_t mask = vmovq_n_u8(m); return this->map( [&](auto a) { return vcleq_u8(a, mask); }).to_bitmask(); } }; // struct simd_input } // namespace simdjson::arm64 #endif // IS_ARM64 #endif // SIMDJSON_ARM64_SIMD_INPUT_H /* end file src/arm64/simd_input.h */ /* begin file src/haswell/simd_input.h */ #ifndef SIMDJSON_HASWELL_SIMD_INPUT_H #define SIMDJSON_HASWELL_SIMD_INPUT_H #ifdef IS_X86_64 TARGET_HASWELL namespace simdjson::haswell { struct simd_input { const __m256i chunks[2]; really_inline simd_input() : chunks{__m256i(), __m256i()} {} really_inline simd_input(const __m256i chunk0, const __m256i chunk1) : chunks{chunk0, chunk1} {} really_inline simd_input(const uint8_t *ptr) : chunks{ _mm256_loadu_si256(reinterpret_cast(ptr + 0*32)), _mm256_loadu_si256(reinterpret_cast(ptr + 1*32)) } {} template really_inline void each(F const& each_chunk) const { each_chunk(this->chunks[0]); each_chunk(this->chunks[1]); } template really_inline simd_input map(F const& map_chunk) const { return simd_input( map_chunk(this->chunks[0]), map_chunk(this->chunks[1]) ); } template really_inline simd_input map(const simd_input b, F const& map_chunk) const { return simd_input( map_chunk(this->chunks[0], b.chunks[0]), map_chunk(this->chunks[1], b.chunks[1]) ); } template really_inline __m256i reduce(F const& reduce_pair) const { return reduce_pair(this->chunks[0], this->chunks[1]); } really_inline uint64_t to_bitmask() const { uint64_t r_lo = static_cast(_mm256_movemask_epi8(this->chunks[0])); uint64_t r_hi = _mm256_movemask_epi8(this->chunks[1]); return r_lo | (r_hi << 32); } really_inline simd_input bit_or(const uint8_t m) const { const __m256i mask = _mm256_set1_epi8(m); return this->map( [&](auto a) { return _mm256_or_si256(a, mask); }); } really_inline uint64_t eq(const uint8_t m) const { const __m256i mask = _mm256_set1_epi8(m); return this->map( [&](auto a) { return _mm256_cmpeq_epi8(a, mask); }).to_bitmask(); } really_inline uint64_t lteq(const uint8_t m) const { const __m256i maxval = _mm256_set1_epi8(m); return this->map( [&](auto a) { return _mm256_cmpeq_epi8(_mm256_max_epu8(maxval, a), maxval); }).to_bitmask(); } }; // struct simd_input } // namespace simdjson::haswell UNTARGET_REGION #endif // IS_X86_64 #endif // SIMDJSON_HASWELL_SIMD_INPUT_H /* end file src/haswell/simd_input.h */ /* begin file src/westmere/simd_input.h */ #ifndef SIMDJSON_WESTMERE_SIMD_INPUT_H #define SIMDJSON_WESTMERE_SIMD_INPUT_H #ifdef IS_X86_64 TARGET_WESTMERE namespace simdjson::westmere { struct simd_input { const __m128i chunks[4]; really_inline simd_input() : chunks { __m128i(), __m128i(), __m128i(), __m128i() } {} really_inline simd_input(const __m128i chunk0, const __m128i chunk1, const __m128i chunk2, const __m128i chunk3) : chunks{chunk0, chunk1, chunk2, chunk3} {} really_inline simd_input(const uint8_t *ptr) : simd_input( _mm_loadu_si128(reinterpret_cast(ptr + 0)), _mm_loadu_si128(reinterpret_cast(ptr + 16)), _mm_loadu_si128(reinterpret_cast(ptr + 32)), _mm_loadu_si128(reinterpret_cast(ptr + 48)) ) {} template really_inline void each(F const& each_chunk) const { each_chunk(this->chunks[0]); each_chunk(this->chunks[1]); each_chunk(this->chunks[2]); each_chunk(this->chunks[3]); } template really_inline simd_input map(F const& map_chunk) const { return simd_input( map_chunk(this->chunks[0]), map_chunk(this->chunks[1]), map_chunk(this->chunks[2]), map_chunk(this->chunks[3]) ); } template really_inline simd_input map(const simd_input b, F const& map_chunk) const { return simd_input( map_chunk(this->chunks[0], b.chunks[0]), map_chunk(this->chunks[1], b.chunks[1]), map_chunk(this->chunks[2], b.chunks[2]), map_chunk(this->chunks[3], b.chunks[3]) ); } template really_inline __m128i reduce(F const& reduce_pair) const { __m128i r01 = reduce_pair(this->chunks[0], this->chunks[1]); __m128i r23 = reduce_pair(this->chunks[2], this->chunks[3]); return reduce_pair(r01, r23); } really_inline uint64_t to_bitmask() const { uint64_t r0 = static_cast(_mm_movemask_epi8(this->chunks[0])); uint64_t r1 = _mm_movemask_epi8(this->chunks[1]); uint64_t r2 = _mm_movemask_epi8(this->chunks[2]); uint64_t r3 = _mm_movemask_epi8(this->chunks[3]); return r0 | (r1 << 16) | (r2 << 32) | (r3 << 48); } really_inline simd_input bit_or(const uint8_t m) const { const __m128i mask = _mm_set1_epi8(m); return this->map( [&](auto a) { return _mm_or_si128(a, mask); }); } really_inline uint64_t eq(const uint8_t m) const { const __m128i mask = _mm_set1_epi8(m); return this->map( [&](auto a) { return _mm_cmpeq_epi8(a, mask); }).to_bitmask(); } really_inline uint64_t lteq(const uint8_t m) const { const __m128i maxval = _mm_set1_epi8(m); return this->map( [&](auto a) { return _mm_cmpeq_epi8(_mm_max_epu8(maxval, a), maxval); }).to_bitmask(); } }; // struct simd_input } // namespace simdjson::westmere UNTARGET_REGION #endif // IS_X86_64 #endif // SIMDJSON_WESTMERE_SIMD_INPUT_H /* end file src/westmere/simd_input.h */ /* begin file src/arm64/simdutf8check.h */ // From https://github.com/cyb70289/utf8/blob/master/lemire-neon.c // Adapted from https://github.com/lemire/fastvalidate-utf-8 #ifndef SIMDJSON_ARM64_SIMDUTF8CHECK_H #define SIMDJSON_ARM64_SIMDUTF8CHECK_H // TODO this is different from IS_ARM64 in portability.h, which we use in other places ... #if defined(_ARM_NEON) || defined(__aarch64__) || \ (defined(_MSC_VER) && defined(_M_ARM64)) #include #include #include #include #include #include /* * legal utf-8 byte sequence * http://www.unicode.org/versions/Unicode6.0.0/ch03.pdf - page 94 * * Code Points 1st 2s 3s 4s * U+0000..U+007F 00..7F * U+0080..U+07FF C2..DF 80..BF * U+0800..U+0FFF E0 A0..BF 80..BF * U+1000..U+CFFF E1..EC 80..BF 80..BF * U+D000..U+D7FF ED 80..9F 80..BF * U+E000..U+FFFF EE..EF 80..BF 80..BF * U+10000..U+3FFFF F0 90..BF 80..BF 80..BF * U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF * U+100000..U+10FFFF F4 80..8F 80..BF 80..BF * */ namespace simdjson::arm64 { static const int8_t _nibbles[] = { 1, 1, 1, 1, 1, 1, 1, 1, // 0xxx (ASCII) 0, 0, 0, 0, // 10xx (continuation) 2, 2, // 110x 3, // 1110 4, // 1111, next should be 0 (not checked here) }; static const int8_t _initial_mins[] = { -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, // 10xx => false (int8_t)0xC2, -128, // 110x (int8_t)0xE1, // 1110 (int8_t)0xF1, }; static const int8_t _second_mins[] = { -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, // 10xx => false 127, 127, // 110x => true (int8_t)0xA0, // 1110 (int8_t)0x90, }; struct processed_utf_bytes { int8x16_t raw_bytes; int8x16_t high_nibbles; int8x16_t carried_continuations; }; struct utf8_checker { int8x16_t has_error{}; processed_utf_bytes previous{}; really_inline void add_errors(uint8x16_t errors) { this->has_error = vorrq_s8(this->has_error, errors); } // all byte values must be no larger than 0xF4 really_inline void check_smaller_than_0xF4(int8x16_t current_bytes) { // unsigned, saturates to 0 below max this->has_error = vreinterpretq_s8_u8(vqsubq_u8( vreinterpretq_u8_s8(current_bytes), vdupq_n_u8(0xF4))); } really_inline int8x16_t continuation_lengths(int8x16_t high_nibbles) { return vqtbl1q_s8(vld1q_s8(_nibbles), vreinterpretq_u8_s8(high_nibbles)); } really_inline int8x16_t carry_continuations(int8x16_t initial_lengths, int8x16_t previous_carries) { int8x16_t right1 = vreinterpretq_s8_u8(vqsubq_u8( vreinterpretq_u8_s8(vextq_s8(previous_carries, initial_lengths, 16 - 1)), vdupq_n_u8(1))); int8x16_t sum = vaddq_s8(initial_lengths, right1); int8x16_t right2 = vreinterpretq_s8_u8( vqsubq_u8(vreinterpretq_u8_s8(vextq_s8(previous_carries, sum, 16 - 2)), vdupq_n_u8(2))); return vaddq_s8(sum, right2); } really_inline void check_continuations(int8x16_t initial_lengths, int8x16_t carries) { // overlap || underlap // carry > length && length > 0 || !(carry > length) && !(length > 0) // (carries > length) == (lengths > 0) uint8x16_t overunder = vceqq_u8(vcgtq_s8(carries, initial_lengths), vcgtq_s8(initial_lengths, vdupq_n_s8(0))); this->add_errors( vreinterpretq_s8_u8(overunder) ); } // when 0xED is found, next byte must be no larger than 0x9F // when 0xF4 is found, next byte must be no larger than 0x8F // next byte must be continuation, ie sign bit is set, so signed < is ok really_inline void check_first_continuation_max(int8x16_t current_bytes, int8x16_t off1_current_bytes) { uint8x16_t maskED = vceqq_s8(off1_current_bytes, vdupq_n_s8(0xED)); uint8x16_t maskF4 = vceqq_s8(off1_current_bytes, vdupq_n_s8(0xF4)); uint8x16_t badfollowED = vandq_u8(vcgtq_s8(current_bytes, vdupq_n_s8(0x9F)), maskED); uint8x16_t badfollowF4 = vandq_u8(vcgtq_s8(current_bytes, vdupq_n_s8(0x8F)), maskF4); this->add_errors( vreinterpretq_s8_u8(vorrq_u8(badfollowED, badfollowF4)) ); } // map off1_hibits => error condition // hibits off1 cur // C => < C2 && true // E => < E1 && < A0 // F => < F1 && < 90 // else false && false really_inline void check_overlong(int8x16_t current_bytes, int8x16_t off1_current_bytes, int8x16_t high_nibbles) { int8x16_t off1_hibits = vextq_s8(this->previous.high_nibbles, high_nibbles, 16 - 1); int8x16_t initial_mins = vqtbl1q_s8(vld1q_s8(_initial_mins), vreinterpretq_u8_s8(off1_hibits)); uint8x16_t initial_under = vcgtq_s8(initial_mins, off1_current_bytes); int8x16_t second_mins = vqtbl1q_s8(vld1q_s8(_second_mins), vreinterpretq_u8_s8(off1_hibits)); uint8x16_t second_under = vcgtq_s8(second_mins, current_bytes); this->add_errors( vreinterpretq_s8_u8(vandq_u8(initial_under, second_under)) ); } really_inline void count_nibbles(int8x16_t bytes, struct processed_utf_bytes *answer) { answer->raw_bytes = bytes; answer->high_nibbles = vreinterpretq_s8_u8(vshrq_n_u8(vreinterpretq_u8_s8(bytes), 4)); } // check whether the current bytes are valid UTF-8 // at the end of the function, previous gets updated really_inline void check_utf8_bytes(int8x16_t current_bytes) { struct processed_utf_bytes pb; this->count_nibbles(current_bytes, &pb); this->check_smaller_than_0xF4(current_bytes); int8x16_t initial_lengths = this->continuation_lengths(pb.high_nibbles); pb.carried_continuations = this->carry_continuations(initial_lengths); this->check_continuations(initial_lengths, pb.carried_continuations); int8x16_t off1_current_bytes = vextq_s8(this->previous.raw_bytes, pb.raw_bytes, 16 - 1); this->check_first_continuation_max(current_bytes, off1_current_bytes); this->check_overlong(current_bytes, off1_current_bytes, pb.high_nibbles); this->previous = pb; } // Checks that all bytes are ascii really_inline bool check_ascii_neon(simd_input in) { // checking if the most significant bit is always equal to 0. uint8x16_t high_bit = vdupq_n_u8(0x80); uint8x16_t any_bits_on = in.reduce([&](auto a, auto b) { return vorrq_u8(a, b); }); uint8x16_t high_bit_on = vandq_u8(any_bits_on, high_bit); uint64x2_t v64 = vreinterpretq_u64_u8(high_bit_on); uint32x2_t v32 = vqmovn_u64(v64); uint64x1_t result = vreinterpret_u64_u32(v32); return vget_lane_u64(result, 0) == 0; } really_inline void check_carried_continuations() { const int8x16_t verror = (int8x16_t){9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 1}; this->add_errors( vreinterpretq_s8_u8(vcgtq_s8(this->previous.carried_continuations, verror)) ); } really_inline void check_next_input(uint8x16_t in) { if (this->check_ascii_neon(in)) { // All bytes are ascii. Therefore the byte that was just before must be // ascii too. We only check the byte that was just before simd_input. Nines // are arbitrary values. this->check_carried_continuations(); } else { // it is not ascii so we have to do heavy work this->check_utf8_bytes(vreinterpretq_s8_u8(in)); } } really_inline void check_next_input(simd_input in) { if (this->check_ascii_neon(in)) { // All bytes are ascii. Therefore the byte that was just before must be // ascii too. We only check the byte that was just before simd_input. Nines // are arbitrary values. this->check_carried_continuations(); } else { // it is not ascii so we have to do heavy work in.each([&](auto _in) { this->check_utf8_bytes(vreinterpretq_s8_u8(_in)); }); } } really_inline ErrorValues errors() { uint64x2_t v64 = vreinterpretq_u64_s8(this->has_error); uint32x2_t v32 = vqmovn_u64(v64); uint64x1_t result = vreinterpret_u64_u32(v32); return vget_lane_u64(result, 0) != 0 ? simdjson::UTF8_ERROR : simdjson::SUCCESS; } }; // struct utf8_checker } // namespace simdjson::arm64 #endif // ARM_NEON #endif // SIMDJSON_ARM64_SIMDUTF8CHECK_H /* end file src/arm64/simdutf8check.h */ /* begin file src/haswell/simdutf8check.h */ #ifndef SIMDJSON_HASWELL_SIMDUTF8CHECK_H #define SIMDJSON_HASWELL_SIMDUTF8CHECK_H #include #include #include #ifdef IS_X86_64 /* * legal utf-8 byte sequence * http://www.unicode.org/versions/Unicode6.0.0/ch03.pdf - page 94 * * Code Points 1st 2s 3s 4s * U+0000..U+007F 00..7F * U+0080..U+07FF C2..DF 80..BF * U+0800..U+0FFF E0 A0..BF 80..BF * U+1000..U+CFFF E1..EC 80..BF 80..BF * U+D000..U+D7FF ED 80..9F 80..BF * U+E000..U+FFFF EE..EF 80..BF 80..BF * U+10000..U+3FFFF F0 90..BF 80..BF 80..BF * U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF * U+100000..U+10FFFF F4 80..8F 80..BF 80..BF * */ // all byte values must be no larger than 0xF4 TARGET_HASWELL namespace simdjson::haswell { static inline __m256i push_last_byte_of_a_to_b(__m256i a, __m256i b) { return _mm256_alignr_epi8(b, _mm256_permute2x128_si256(a, b, 0x21), 15); } static inline __m256i push_last_2bytes_of_a_to_b(__m256i a, __m256i b) { return _mm256_alignr_epi8(b, _mm256_permute2x128_si256(a, b, 0x21), 14); } struct processed_utf_bytes { __m256i raw_bytes; __m256i high_nibbles; __m256i carried_continuations; }; struct utf8_checker { __m256i has_error; processed_utf_bytes previous; utf8_checker() { has_error = _mm256_setzero_si256(); previous.raw_bytes = _mm256_setzero_si256(); previous.high_nibbles = _mm256_setzero_si256(); previous.carried_continuations = _mm256_setzero_si256(); } really_inline void add_errors(__m256i errors) { this->has_error = _mm256_or_si256(this->has_error, errors); } // all byte values must be no larger than 0xF4 really_inline void check_smaller_than_0xF4(__m256i current_bytes) { // unsigned, saturates to 0 below max this->add_errors( _mm256_subs_epu8(current_bytes, _mm256_set1_epi8(0xF4u)) ); } really_inline __m256i continuation_lengths(__m256i high_nibbles) { return _mm256_shuffle_epi8( _mm256_setr_epi8(1, 1, 1, 1, 1, 1, 1, 1, // 0xxx (ASCII) 0, 0, 0, 0, // 10xx (continuation) 2, 2, // 110x 3, // 1110 4, // 1111, next should be 0 (not checked here) 1, 1, 1, 1, 1, 1, 1, 1, // 0xxx (ASCII) 0, 0, 0, 0, // 10xx (continuation) 2, 2, // 110x 3, // 1110 4), // 1111, next should be 0 (not checked here) high_nibbles); } really_inline __m256i carry_continuations(__m256i initial_lengths) { __m256i right1 = _mm256_subs_epu8( push_last_byte_of_a_to_b(this->previous.carried_continuations, initial_lengths), _mm256_set1_epi8(1)); __m256i sum = _mm256_add_epi8(initial_lengths, right1); __m256i right2 = _mm256_subs_epu8( push_last_2bytes_of_a_to_b(this->previous.carried_continuations, sum), _mm256_set1_epi8(2)); return _mm256_add_epi8(sum, right2); } really_inline void check_continuations(__m256i initial_lengths, __m256i carries) { // overlap || underlap // carry > length && length > 0 || !(carry > length) && !(length > 0) // (carries > length) == (lengths > 0) __m256i overunder = _mm256_cmpeq_epi8( _mm256_cmpgt_epi8(carries, initial_lengths), _mm256_cmpgt_epi8(initial_lengths, _mm256_setzero_si256())); this->add_errors( overunder ); } really_inline void check_carried_continuations() { this->add_errors( _mm256_cmpgt_epi8(this->previous.carried_continuations, _mm256_setr_epi8(9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 1)) ); } // when 0xED is found, next byte must be no larger than 0x9F // when 0xF4 is found, next byte must be no larger than 0x8F // next byte must be continuation, ie sign bit is set, so signed < is ok really_inline void check_first_continuation_max(__m256i current_bytes, __m256i off1_current_bytes) { __m256i maskED = _mm256_cmpeq_epi8(off1_current_bytes, _mm256_set1_epi8(0xEDu)); __m256i maskF4 = _mm256_cmpeq_epi8(off1_current_bytes, _mm256_set1_epi8(0xF4u)); __m256i badfollowED = _mm256_and_si256( _mm256_cmpgt_epi8(current_bytes, _mm256_set1_epi8(0x9Fu)), maskED); __m256i badfollowF4 = _mm256_and_si256( _mm256_cmpgt_epi8(current_bytes, _mm256_set1_epi8(0x8Fu)), maskF4); this->add_errors( _mm256_or_si256(badfollowED, badfollowF4) ); } // map off1_hibits => error condition // hibits off1 cur // C => < C2 && true // E => < E1 && < A0 // F => < F1 && < 90 // else false && false really_inline void check_overlong(__m256i current_bytes, __m256i off1_current_bytes, __m256i high_nibbles) { __m256i off1_high_nibbles = push_last_byte_of_a_to_b(this->previous.high_nibbles, high_nibbles); __m256i initial_mins = _mm256_shuffle_epi8( _mm256_setr_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, // 10xx => false 0xC2u, -128, // 110x 0xE1u, // 1110 0xF1u, // 1111 -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, // 10xx => false 0xC2u, -128, // 110x 0xE1u, // 1110 0xF1u), // 1111 off1_high_nibbles); __m256i initial_under = _mm256_cmpgt_epi8(initial_mins, off1_current_bytes); __m256i second_mins = _mm256_shuffle_epi8( _mm256_setr_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, // 10xx => false 127, 127, // 110x => true 0xA0u, // 1110 0x90u, // 1111 -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, // 10xx => false 127, 127, // 110x => true 0xA0u, // 1110 0x90u), // 1111 off1_high_nibbles); __m256i second_under = _mm256_cmpgt_epi8(second_mins, current_bytes); this->add_errors( _mm256_and_si256(initial_under, second_under) ); } really_inline void count_nibbles(__m256i bytes, struct processed_utf_bytes *answer) { answer->raw_bytes = bytes; answer->high_nibbles = _mm256_and_si256(_mm256_srli_epi16(bytes, 4), _mm256_set1_epi8(0x0F)); } // check whether the current bytes are valid UTF-8 // at the end of the function, previous gets updated really_inline void check_utf8_bytes(__m256i current_bytes) { struct processed_utf_bytes pb {}; this->count_nibbles(current_bytes, &pb); this->check_smaller_than_0xF4(current_bytes); __m256i initial_lengths = this->continuation_lengths(pb.high_nibbles); pb.carried_continuations = this->carry_continuations(initial_lengths); this->check_continuations(initial_lengths, pb.carried_continuations); __m256i off1_current_bytes = push_last_byte_of_a_to_b(this->previous.raw_bytes, pb.raw_bytes); this->check_first_continuation_max(current_bytes, off1_current_bytes); this->check_overlong(current_bytes, off1_current_bytes, pb.high_nibbles); this->previous = pb; } really_inline void check_next_input(__m256i in) { __m256i high_bit = _mm256_set1_epi8(0x80u); if (likely(_mm256_testz_si256(in, high_bit) == 1)) { this->check_carried_continuations(); } else { this->check_utf8_bytes(in); } } really_inline void check_next_input(simd_input in) { __m256i high_bit = _mm256_set1_epi8(0x80u); __m256i any_bits_on = in.reduce([&](auto a, auto b) { return _mm256_or_si256(a, b); }); if (likely(_mm256_testz_si256(any_bits_on, high_bit) == 1)) { // it is ascii, we just check carried continuations. this->check_carried_continuations(); } else { // it is not ascii so we have to do heavy work in.each([&](auto _in) { check_utf8_bytes(_in); }); } } really_inline ErrorValues errors() { return _mm256_testz_si256(this->has_error, this->has_error) == 0 ? simdjson::UTF8_ERROR : simdjson::SUCCESS; } }; // struct utf8_checker }; // namespace simdjson::haswell UNTARGET_REGION // haswell #endif // IS_X86_64 #endif // SIMDJSON_HASWELL_SIMDUTF8CHECK_H /* end file src/haswell/simdutf8check.h */ /* begin file src/westmere/simdutf8check.h */ #ifndef SIMDJSON_WESTMERE_SIMDUTF8CHECK_H #define SIMDJSON_WESTMERE_SIMDUTF8CHECK_H #include #include #include #ifdef IS_X86_64 /* * legal utf-8 byte sequence * http://www.unicode.org/versions/Unicode6.0.0/ch03.pdf - page 94 * * Code Points 1st 2s 3s 4s * U+0000..U+007F 00..7F * U+0080..U+07FF C2..DF 80..BF * U+0800..U+0FFF E0 A0..BF 80..BF * U+1000..U+CFFF E1..EC 80..BF 80..BF * U+D000..U+D7FF ED 80..9F 80..BF * U+E000..U+FFFF EE..EF 80..BF 80..BF * U+10000..U+3FFFF F0 90..BF 80..BF 80..BF * U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF * U+100000..U+10FFFF F4 80..8F 80..BF 80..BF * */ // all byte values must be no larger than 0xF4 /********** sse code **********/ TARGET_WESTMERE namespace simdjson::westmere { struct processed_utf_bytes { __m128i raw_bytes; __m128i high_nibbles; __m128i carried_continuations; }; struct utf8_checker { __m128i has_error = _mm_setzero_si128(); processed_utf_bytes previous{ _mm_setzero_si128(), // raw_bytes _mm_setzero_si128(), // high_nibbles _mm_setzero_si128() // carried_continuations }; really_inline void add_errors(__m128i errors) { this->has_error = _mm_or_si128(errors, this->has_error); } // all byte values must be no larger than 0xF4 really_inline void check_smaller_than_0xF4(__m128i current_bytes) { // unsigned, saturates to 0 below max this->add_errors( _mm_subs_epu8(current_bytes, _mm_set1_epi8(0xF4u)) ); } really_inline __m128i continuation_lengths(__m128i high_nibbles) { return _mm_shuffle_epi8( _mm_setr_epi8(1, 1, 1, 1, 1, 1, 1, 1, // 0xxx (ASCII) 0, 0, 0, 0, // 10xx (continuation) 2, 2, // 110x 3, // 1110 4), // 1111, next should be 0 (not checked here) high_nibbles); } really_inline __m128i carry_continuations(__m128i initial_lengths) { __m128i right1 = _mm_subs_epu8(_mm_alignr_epi8(initial_lengths, this->previous.carried_continuations, 16 - 1), _mm_set1_epi8(1)); __m128i sum = _mm_add_epi8(initial_lengths, right1); __m128i right2 = _mm_subs_epu8(_mm_alignr_epi8(sum, this->previous.carried_continuations, 16 - 2), _mm_set1_epi8(2)); return _mm_add_epi8(sum, right2); } really_inline void check_continuations(__m128i initial_lengths, __m128i carries) { // overlap || underlap // carry > length && length > 0 || !(carry > length) && !(length > 0) // (carries > length) == (lengths > 0) __m128i overunder = _mm_cmpeq_epi8(_mm_cmpgt_epi8(carries, initial_lengths), _mm_cmpgt_epi8(initial_lengths, _mm_setzero_si128())); this->add_errors( overunder ); } // when 0xED is found, next byte must be no larger than 0x9F // when 0xF4 is found, next byte must be no larger than 0x8F // next byte must be continuation, ie sign bit is set, so signed < is ok really_inline void check_first_continuation_max(__m128i current_bytes, __m128i off1_current_bytes) { __m128i maskED = _mm_cmpeq_epi8(off1_current_bytes, _mm_set1_epi8(0xEDu)); __m128i maskF4 = _mm_cmpeq_epi8(off1_current_bytes, _mm_set1_epi8(0xF4u)); __m128i badfollowED = _mm_and_si128( _mm_cmpgt_epi8(current_bytes, _mm_set1_epi8(0x9Fu)), maskED); __m128i badfollowF4 = _mm_and_si128( _mm_cmpgt_epi8(current_bytes, _mm_set1_epi8(0x8Fu)), maskF4); this->add_errors( _mm_or_si128(badfollowED, badfollowF4) ); } // map off1_hibits => error condition // hibits off1 cur // C => < C2 && true // E => < E1 && < A0 // F => < F1 && < 90 // else false && false really_inline void check_overlong(__m128i current_bytes, __m128i off1_current_bytes, __m128i high_nibbles) { __m128i off1_hibits = _mm_alignr_epi8(high_nibbles, this->previous.high_nibbles, 16 - 1); __m128i initial_mins = _mm_shuffle_epi8( _mm_setr_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, // 10xx => false 0xC2u, -128, // 110x 0xE1u, // 1110 0xF1u), off1_hibits); __m128i initial_under = _mm_cmpgt_epi8(initial_mins, off1_current_bytes); __m128i second_mins = _mm_shuffle_epi8( _mm_setr_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, // 10xx => false 127, 127, // 110x => true 0xA0u, // 1110 0x90u), off1_hibits); __m128i second_under = _mm_cmpgt_epi8(second_mins, current_bytes); this->add_errors( _mm_and_si128(initial_under, second_under) ); } really_inline void count_nibbles(__m128i bytes, struct processed_utf_bytes *answer) { answer->raw_bytes = bytes; answer->high_nibbles = _mm_and_si128(_mm_srli_epi16(bytes, 4), _mm_set1_epi8(0x0F)); } // check whether the current bytes are valid UTF-8 // at the end of the function, previous gets updated really_inline void check_utf8_bytes(__m128i current_bytes) { struct processed_utf_bytes pb; this->count_nibbles(current_bytes, &pb); this->check_smaller_than_0xF4(current_bytes); __m128i initial_lengths = this->continuation_lengths(pb.high_nibbles); pb.carried_continuations = this->carry_continuations(initial_lengths); this->check_continuations(initial_lengths, pb.carried_continuations); __m128i off1_current_bytes = _mm_alignr_epi8(pb.raw_bytes, this->previous.raw_bytes, 16 - 1); this->check_first_continuation_max(current_bytes, off1_current_bytes); this->check_overlong(current_bytes, off1_current_bytes, pb.high_nibbles); this->previous = pb; } really_inline void check_carried_continuations() { this->has_error = _mm_cmpgt_epi8(this->previous.carried_continuations, _mm_setr_epi8(9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 1)); } really_inline void check_next_input(__m128i in) { __m128i high_bit = _mm_set1_epi8(0x80u); if (_mm_testz_si128( in, high_bit) == 1) { // it is ascii, we just check continuations this->check_carried_continuations(); } else { // it is not ascii so we have to do heavy work this->check_utf8_bytes(in); } } really_inline void check_next_input(simd_input in) { __m128i high_bit = _mm_set1_epi8(0x80u); __m128i any_bits_on = in.reduce([&](auto a, auto b) { return _mm_or_si128(a, b); }); if (_mm_testz_si128(any_bits_on, high_bit) == 1) { // it is ascii, we just check continuations this->check_carried_continuations(); } else { // it is not ascii so we have to do heavy work in.each([&](auto _in) { this->check_utf8_bytes(_in); }); } } really_inline ErrorValues errors() { return _mm_testz_si128(this->has_error, this->has_error) == 0 ? simdjson::UTF8_ERROR : simdjson::SUCCESS; } }; // struct utf8_checker } // namespace simdjson::westmere UNTARGET_REGION // westmere #endif // IS_X86_64 #endif /* end file src/westmere/simdutf8check.h */ /* begin file src/arm64/stage1_find_marks.h */ #ifndef SIMDJSON_ARM64_STAGE1_FIND_MARKS_H #define SIMDJSON_ARM64_STAGE1_FIND_MARKS_H #ifdef IS_ARM64 namespace simdjson::arm64 { really_inline uint64_t compute_quote_mask(const uint64_t quote_bits) { #ifdef __ARM_FEATURE_CRYPTO // some ARM processors lack this extension return vmull_p64(-1ULL, quote_bits); #else return portable_compute_quote_mask(quote_bits); #endif } really_inline void find_whitespace_and_operators( const simd_input in, uint64_t &whitespace, uint64_t &op) { const uint8x16_t low_nibble_mask = (uint8x16_t){16, 0, 0, 0, 0, 0, 0, 0, 0, 8, 12, 1, 2, 9, 0, 0}; const uint8x16_t high_nibble_mask = (uint8x16_t){8, 0, 18, 4, 0, 1, 0, 1, 0, 0, 0, 3, 2, 1, 0, 0}; const uint8x16_t low_nib_and_mask = vmovq_n_u8(0xf); auto v = in.map([&](auto chunk) { uint8x16_t nib_lo = vandq_u8(chunk, low_nib_and_mask); uint8x16_t nib_hi = vshrq_n_u8(chunk, 4); uint8x16_t shuf_lo = vqtbl1q_u8(low_nibble_mask, nib_lo); uint8x16_t shuf_hi = vqtbl1q_u8(high_nibble_mask, nib_hi); return vandq_u8(shuf_lo, shuf_hi); }); const uint8x16_t operator_shufti_mask = vmovq_n_u8(0x7); op = v.map([&](auto _v) { return vtstq_u8(_v, operator_shufti_mask); }).to_bitmask(); const uint8x16_t whitespace_shufti_mask = vmovq_n_u8(0x18); whitespace = v.map([&](auto _v) { return vtstq_u8(_v, whitespace_shufti_mask); }).to_bitmask(); } // This file contains a non-architecture-specific version of "flatten" used in stage1. // It is intended to be included multiple times and compiled multiple times // We assume the file in which it is include already includes // "simdjson/stage1_find_marks.h" (this simplifies amalgation) #ifdef SIMDJSON_NAIVE_FLATTEN // useful for benchmarking // This is just a naive implementation. It should be normally // disable, but can be used for research purposes to compare // again our optimized version. really_inline void flatten_bits(uint32_t *base_ptr, uint32_t &base, uint32_t idx, uint64_t bits) { uint32_t *out_ptr = base_ptr + base; idx -= 64; while (bits != 0) { out_ptr[0] = idx + trailing_zeroes(bits); bits = bits & (bits - 1); out_ptr++; } base = (out_ptr - base_ptr); } #else // SIMDJSON_NAIVE_FLATTEN // flatten out values in 'bits' assuming that they are are to have values of idx // plus their position in the bitvector, and store these indexes at // base_ptr[base] incrementing base as we go // will potentially store extra values beyond end of valid bits, so base_ptr // needs to be large enough to handle this really_inline void flatten_bits(uint32_t *&base_ptr, uint32_t idx, uint64_t bits) { // In some instances, the next branch is expensive because it is mispredicted. // Unfortunately, in other cases, // it helps tremendously. if (bits == 0) return; uint32_t cnt = hamming(bits); idx -= 64; // Do the first 8 all together for (int i=0; i<8; i++) { base_ptr[i] = idx + trailing_zeroes(bits); bits = bits & (bits - 1); } // Do the next 8 all together (we hope in most cases it won't happen at all // and the branch is easily predicted). if (unlikely(cnt > 8)) { for (int i=8; i<16; i++) { base_ptr[i] = idx + trailing_zeroes(bits); bits = bits & (bits - 1); } // Most files don't have 16+ structurals per block, so we take several basically guaranteed // branch mispredictions here. 16+ structurals per block means either punctuation ({} [] , :) // or the start of a value ("abc" true 123) every 4 characters. if (unlikely(cnt > 16)) { uint32_t i = 16; do { base_ptr[i] = idx + trailing_zeroes(bits); bits = bits & (bits - 1); i++; } while (i < cnt); } } base_ptr += cnt; } #endif // SIMDJSON_NAIVE_FLATTEN // This file contains the common code every implementation uses in stage1 // It is intended to be included multiple times and compiled multiple times // We assume the file in which it is included already includes // "simdjson/stage1_find_marks.h" (this simplifies amalgation) // return a bitvector indicating where we have characters that end an odd-length // sequence of backslashes (and thus change the behavior of the next character // to follow). A even-length sequence of backslashes, and, for that matter, the // largest even-length prefix of our odd-length sequence of backslashes, simply // modify the behavior of the backslashes themselves. // We also update the prev_iter_ends_odd_backslash reference parameter to // indicate whether we end an iteration on an odd-length sequence of // backslashes, which modifies our subsequent search for odd-length // sequences of backslashes in an obvious way. really_inline uint64_t follows_odd_sequence_of(const uint64_t match, uint64_t &overflow) { const uint64_t even_bits = 0x5555555555555555ULL; const uint64_t odd_bits = ~even_bits; uint64_t start_edges = match & ~(match << 1); /* flip lowest if we have an odd-length run at the end of the prior * iteration */ uint64_t even_start_mask = even_bits ^ overflow; uint64_t even_starts = start_edges & even_start_mask; uint64_t odd_starts = start_edges & ~even_start_mask; uint64_t even_carries = match + even_starts; uint64_t odd_carries; /* must record the carry-out of our odd-carries out of bit 63; this * indicates whether the sense of any edge going to the next iteration * should be flipped */ bool new_overflow = add_overflow(match, odd_starts, &odd_carries); odd_carries |= overflow; /* push in bit zero as a * potential end if we had an * odd-numbered run at the * end of the previous * iteration */ overflow = new_overflow ? 0x1ULL : 0x0ULL; uint64_t even_carry_ends = even_carries & ~match; uint64_t odd_carry_ends = odd_carries & ~match; uint64_t even_start_odd_end = even_carry_ends & odd_bits; uint64_t odd_start_even_end = odd_carry_ends & even_bits; uint64_t odd_ends = even_start_odd_end | odd_start_even_end; return odd_ends; } // // Check if the current character immediately follows a matching character. // // For example, this checks for quotes with backslashes in front of them: // // const uint64_t backslashed_quote = in.eq('"') & immediately_follows(in.eq('\'), prev_backslash); // really_inline uint64_t follows(const uint64_t match, uint64_t &overflow) { const uint64_t result = match << 1 | overflow; overflow = match >> 63; return result; } // // Check if the current character follows a matching character, with possible "filler" between. // For example, this checks for empty curly braces, e.g. // // in.eq('}') & follows(in.eq('['), in.eq(' '), prev_empty_array) // { * } // really_inline uint64_t follows(const uint64_t match, const uint64_t filler, uint64_t &overflow ) { uint64_t follows_match = follows(match, overflow); uint64_t result; overflow |= add_overflow(follows_match, filler, &result); return result; } really_inline ErrorValues detect_errors_on_eof( uint64_t &unescaped_chars_error, const uint64_t prev_in_string) { if (prev_in_string) { return UNCLOSED_STRING; } if (unescaped_chars_error) { return UNESCAPED_CHARS; } return SUCCESS; } // // Return a mask of all string characters plus end quotes. // // prev_escaped is overflow saying whether the next character is escaped. // prev_in_string is overflow saying whether we're still in a string. // // Backslash sequences outside of quotes will be detected in stage 2. // really_inline uint64_t find_strings(const simd_input in, uint64_t &prev_escaped, uint64_t &prev_in_string) { const uint64_t backslash = in.eq('\\'); const uint64_t escaped = follows_odd_sequence_of(backslash, prev_escaped); const uint64_t quote = in.eq('"') & ~escaped; // compute_quote_mask returns start quote plus string contents. const uint64_t in_string = compute_quote_mask(quote) ^ prev_in_string; /* right shift of a signed value expected to be well-defined and standard * compliant as of C++20, * John Regher from Utah U. says this is fine code */ prev_in_string = static_cast(static_cast(in_string) >> 63); // Use ^ to turn the beginning quote off, and the end quote on. return in_string ^ quote; } really_inline uint64_t invalid_string_bytes(const uint64_t unescaped, const uint64_t quote_mask) { /* 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). * https://tools.ietf.org/html/rfc8259 */ return quote_mask & unescaped; } // // Determine which characters are *structural*: // - braces: [] and {} // - the start of primitives (123, true, false, null) // - the start of invalid non-whitespace (+, &, ture, UTF-8) // // Also detects value sequence errors: // - two values with no separator between ("hello" "world") // - separators with no values ([1,] [1,,]and [,2]) // // This method will find all of the above whether it is in a string or not. // // To reduce dependency on the expensive "what is in a string" computation, this method treats the // contents of a string the same as content outside. Errors and structurals inside the string or on // the trailing quote will need to be removed later when the correct string information is known. // really_inline uint64_t find_potential_structurals(const simd_input in, uint64_t &prev_primitive) { // These use SIMD so let's kick them off before running the regular 64-bit stuff ... uint64_t whitespace, op; find_whitespace_and_operators(in, whitespace, op); // Detect the start of a run of primitive characters. Includes numbers, booleans, and strings ("). // Everything except whitespace, braces, colon and comma. const uint64_t primitive = ~(op | whitespace); const uint64_t follows_primitive = follows(primitive, prev_primitive); const uint64_t start_primitive = primitive & ~follows_primitive; // Return final structurals return op | start_primitive; } static const size_t STEP_SIZE = 128; // // Find the important bits of JSON in a 128-byte chunk, and add them to : // // // // PERF NOTES: // We pipe 2 inputs through these stages: // 1. Load JSON into registers. This takes a long time and is highly parallelizable, so we load // 2 inputs' worth at once so that by the time step 2 is looking for them input, it's available. // 2. Scan the JSON for critical data: strings, primitives and operators. This is the critical path. // The output of step 1 depends entirely on this information. These functions don't quite use // up enough CPU: the second half of the functions is highly serial, only using 1 execution core // at a time. The second input's scans has some dependency on the first ones finishing it, but // they can make a lot of progress before they need that information. // 3. Step 1 doesn't use enough capacity, so we run some extra stuff while we're waiting for that // to finish: utf-8 checks and generating the output from the last iteration. // // The reason we run 2 inputs at a time, is steps 2 and 3 are *still* not enough to soak up all // available capacity with just one input. Running 2 at a time seems to give the CPU a good enough // workout. // really_inline void find_structural_bits_128( const uint8_t *buf, const size_t idx, uint32_t *&base_ptr, uint64_t &prev_escaped, uint64_t &prev_in_string, uint64_t &prev_primitive, uint64_t &prev_structurals, uint64_t &unescaped_chars_error, utf8_checker &utf8_state) { // // Load up all 128 bytes into SIMD registers // simd_input in_1(buf); simd_input in_2(buf+64); // // Find the strings and potential structurals (operators / primitives). // // This will include false structurals that are *inside* strings--we'll filter strings out // before we return. // uint64_t string_1 = find_strings(in_1, prev_escaped, prev_in_string); uint64_t structurals_1 = find_potential_structurals(in_1, prev_primitive); uint64_t string_2 = find_strings(in_2, prev_escaped, prev_in_string); uint64_t structurals_2 = find_potential_structurals(in_2, prev_primitive); // // Do miscellaneous work while the processor is busy calculating strings and structurals. // // After that, weed out structurals that are inside strings and find invalid string characters. // uint64_t unescaped_1 = in_1.lteq(0x1F); utf8_state.check_next_input(in_1); flatten_bits(base_ptr, idx, prev_structurals); // Output *last* iteration's structurals to ParsedJson prev_structurals = structurals_1 & ~string_1; unescaped_chars_error |= unescaped_1 & string_1; uint64_t unescaped_2 = in_2.lteq(0x1F); utf8_state.check_next_input(in_2); flatten_bits(base_ptr, idx+64, prev_structurals); // Output *last* iteration's structurals to ParsedJson prev_structurals = structurals_2 & ~string_2; unescaped_chars_error |= unescaped_2 & string_2; } int find_structural_bits(const uint8_t *buf, size_t len, simdjson::ParsedJson &pj) { if (unlikely(len > pj.byte_capacity)) { std::cerr << "Your ParsedJson object only supports documents up to " << pj.byte_capacity << " bytes but you are trying to process " << len << " bytes" << std::endl; return simdjson::CAPACITY; } uint32_t *base_ptr = pj.structural_indexes; utf8_checker utf8_state; // Whether the first character of the next iteration is escaped. uint64_t prev_escaped = 0ULL; // Whether the last iteration was still inside a string (all 1's = true, all 0's = false). uint64_t prev_in_string = 0ULL; // Whether the last character of the previous iteration is a primitive value character // (anything except whitespace, braces, comma or colon). uint64_t prev_primitive = 0ULL; // Mask of structural characters from the last iteration. // Kept around for performance reasons, so we can call flatten_bits to soak up some unused // CPU capacity while the next iteration is busy with an expensive clmul in compute_quote_mask. uint64_t structurals = 0; size_t lenminusstep = len < STEP_SIZE ? 0 : len - STEP_SIZE; size_t idx = 0; // Errors with unescaped characters in strings (ASCII codepoints < 0x20) uint64_t unescaped_chars_error = 0; for (; idx < lenminusstep; idx += STEP_SIZE) { find_structural_bits_128(&buf[idx], idx, base_ptr, prev_escaped, prev_in_string, prev_primitive, structurals, unescaped_chars_error, utf8_state); } /* If we have a final chunk of less than 64 bytes, pad it to 64 with * spaces before processing it (otherwise, we risk invalidating the UTF-8 * checks). */ if (likely(idx < len)) { uint8_t tmp_buf[STEP_SIZE]; memset(tmp_buf, 0x20, STEP_SIZE); memcpy(tmp_buf, buf + idx, len - idx); find_structural_bits_128(&tmp_buf[0], idx, base_ptr, prev_escaped, prev_in_string, prev_primitive, structurals, unescaped_chars_error, utf8_state); idx += STEP_SIZE; } /* finally, flatten out the remaining structurals from the last iteration */ flatten_bits(base_ptr, idx, structurals); simdjson::ErrorValues error = detect_errors_on_eof(unescaped_chars_error, prev_in_string); if (unlikely(error != simdjson::SUCCESS)) { return error; } pj.n_structural_indexes = base_ptr - pj.structural_indexes; /* a valid JSON file cannot have zero structural indexes - we should have * found something */ if (unlikely(pj.n_structural_indexes == 0u)) { return simdjson::EMPTY; } if (unlikely(pj.structural_indexes[pj.n_structural_indexes - 1] > len)) { return simdjson::UNEXPECTED_ERROR; } if (len != pj.structural_indexes[pj.n_structural_indexes - 1]) { /* the string might not be NULL terminated, but we add a virtual NULL * ending character. */ pj.structural_indexes[pj.n_structural_indexes++] = len; } /* make it safe to dereference one beyond this array */ pj.structural_indexes[pj.n_structural_indexes] = 0; return utf8_state.errors(); } } // namespace simdjson::arm64 namespace simdjson { template <> int find_structural_bits(const uint8_t *buf, size_t len, simdjson::ParsedJson &pj) { return arm64::find_structural_bits(buf, len, pj); } } // namespace simdjson #endif // IS_ARM64 #endif // SIMDJSON_ARM64_STAGE1_FIND_MARKS_H /* end file src/arm64/stage1_find_marks.h */ /* begin file src/haswell/stage1_find_marks.h */ #ifndef SIMDJSON_HASWELL_STAGE1_FIND_MARKS_H #define SIMDJSON_HASWELL_STAGE1_FIND_MARKS_H #ifdef IS_X86_64 TARGET_HASWELL namespace simdjson::haswell { really_inline uint64_t compute_quote_mask(const uint64_t quote_bits) { // There should be no such thing with a processing supporting avx2 // but not clmul. uint64_t quote_mask = _mm_cvtsi128_si64(_mm_clmulepi64_si128( _mm_set_epi64x(0ULL, quote_bits), _mm_set1_epi8(0xFFu), 0)); return quote_mask; } really_inline void find_whitespace_and_operators( const simd_input in, uint64_t &whitespace, uint64_t &op) { #ifdef SIMDJSON_NAIVE_STRUCTURAL // You should never need this naive approach, but it can be useful // for research purposes const __m256i mask_open_brace = _mm256_set1_epi8(0x7b); const __m256i mask_close_brace = _mm256_set1_epi8(0x7d); const __m256i mask_open_bracket = _mm256_set1_epi8(0x5b); const __m256i mask_close_bracket = _mm256_set1_epi8(0x5d); const __m256i mask_column = _mm256_set1_epi8(0x3a); const __m256i mask_comma = _mm256_set1_epi8(0x2c); op = in.map([&](auto in) { __m256i op = _mm256_cmpeq_epi8(in, mask_open_brace); op = _mm256_or_si256(op, _mm256_cmpeq_epi8(in, mask_close_brace)); op = _mm256_or_si256(op, _mm256_cmpeq_epi8(in, mask_open_bracket)); op = _mm256_or_si256(op, _mm256_cmpeq_epi8(in, mask_close_bracket)); op = _mm256_or_si256(op, _mm256_cmpeq_epi8(in, mask_column)); op = _mm256_or_si256(op, _mm256_cmpeq_epi8(in, mask_comma)); return op; }).to_bitmask(); const __m256i mask_space = _mm256_set1_epi8(0x20); const __m256i mask_linefeed = _mm256_set1_epi8(0x0a); const __m256i mask_tab = _mm256_set1_epi8(0x09); const __m256i mask_carriage = _mm256_set1_epi8(0x0d); whitespace = in.map([&](auto in) { __m256i space = _mm256_cmpeq_epi8(in, mask_space); space = _mm256_or_si256(space, _mm256_cmpeq_epi8(in, mask_linefeed)); space = _mm256_or_si256(space, _mm256_cmpeq_epi8(in, mask_tab)); space = _mm256_or_si256(space, _mm256_cmpeq_epi8(in, mask_carriage)); return space; }).to_bitmask(); // end of naive approach #else // SIMDJSON_NAIVE_STRUCTURAL // clang-format off const __m256i operator_table = _mm256_setr_epi8(44, 125, 0, 0, 0xc0u, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58, 123, 44, 125, 0, 0, 0xc0u, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58, 123); const __m256i white_table = _mm256_setr_epi8( 32, 100, 100, 100, 17, 100, 113, 2, 100, 9, 10, 112, 100, 13, 100, 100, 32, 100, 100, 100, 17, 100, 113, 2, 100, 9, 10, 112, 100, 13, 100, 100); // clang-format on const __m256i op_offset = _mm256_set1_epi8(0xd4u); const __m256i op_mask = _mm256_set1_epi8(32); whitespace = in.map([&](auto _in) { return _mm256_cmpeq_epi8(_in, _mm256_shuffle_epi8(white_table, _in)); }).to_bitmask(); op = in.map([&](auto _in) { const __m256i r1 = _mm256_add_epi8(op_offset, _in); const __m256i r2 = _mm256_or_si256(_in, op_mask); const __m256i r3 = _mm256_shuffle_epi8(operator_table, r1); return _mm256_cmpeq_epi8(r2, r3); }).to_bitmask(); #endif // else SIMDJSON_NAIVE_STRUCTURAL } // flatten out values in 'bits' assuming that they are are to have values of idx // plus their position in the bitvector, and store these indexes at // base_ptr[base] incrementing base as we go // will potentially store extra values beyond end of valid bits, so base_ptr // needs to be large enough to handle this really_inline void flatten_bits(uint32_t *&base_ptr, uint32_t idx, uint64_t bits) { // In some instances, the next branch is expensive because it is mispredicted. // Unfortunately, in other cases, // it helps tremendously. if (bits == 0) return; uint32_t cnt = _mm_popcnt_u64(bits); idx -= 64; // Do the first 8 all together for (int i=0; i<8; i++) { base_ptr[i] = idx + trailing_zeroes(bits); bits = _blsr_u64(bits); } // Do the next 8 all together (we hope in most cases it won't happen at all // and the branch is easily predicted). if (unlikely(cnt > 8)) { for (int i=8; i<16; i++) { base_ptr[i] = idx + trailing_zeroes(bits); bits = _blsr_u64(bits); } // Most files don't have 16+ structurals per block, so we take several basically guaranteed // branch mispredictions here. 16+ structurals per block means either punctuation ({} [] , :) // or the start of a value ("abc" true 123) every four characters. if (unlikely(cnt > 16)) { uint32_t i = 16; do { base_ptr[i] = idx + trailing_zeroes(bits); bits = _blsr_u64(bits); i++; } while (i < cnt); } } base_ptr += cnt; } // This file contains the common code every implementation uses in stage1 // It is intended to be included multiple times and compiled multiple times // We assume the file in which it is included already includes // "simdjson/stage1_find_marks.h" (this simplifies amalgation) // return a bitvector indicating where we have characters that end an odd-length // sequence of backslashes (and thus change the behavior of the next character // to follow). A even-length sequence of backslashes, and, for that matter, the // largest even-length prefix of our odd-length sequence of backslashes, simply // modify the behavior of the backslashes themselves. // We also update the prev_iter_ends_odd_backslash reference parameter to // indicate whether we end an iteration on an odd-length sequence of // backslashes, which modifies our subsequent search for odd-length // sequences of backslashes in an obvious way. really_inline uint64_t follows_odd_sequence_of(const uint64_t match, uint64_t &overflow) { const uint64_t even_bits = 0x5555555555555555ULL; const uint64_t odd_bits = ~even_bits; uint64_t start_edges = match & ~(match << 1); /* flip lowest if we have an odd-length run at the end of the prior * iteration */ uint64_t even_start_mask = even_bits ^ overflow; uint64_t even_starts = start_edges & even_start_mask; uint64_t odd_starts = start_edges & ~even_start_mask; uint64_t even_carries = match + even_starts; uint64_t odd_carries; /* must record the carry-out of our odd-carries out of bit 63; this * indicates whether the sense of any edge going to the next iteration * should be flipped */ bool new_overflow = add_overflow(match, odd_starts, &odd_carries); odd_carries |= overflow; /* push in bit zero as a * potential end if we had an * odd-numbered run at the * end of the previous * iteration */ overflow = new_overflow ? 0x1ULL : 0x0ULL; uint64_t even_carry_ends = even_carries & ~match; uint64_t odd_carry_ends = odd_carries & ~match; uint64_t even_start_odd_end = even_carry_ends & odd_bits; uint64_t odd_start_even_end = odd_carry_ends & even_bits; uint64_t odd_ends = even_start_odd_end | odd_start_even_end; return odd_ends; } // // Check if the current character immediately follows a matching character. // // For example, this checks for quotes with backslashes in front of them: // // const uint64_t backslashed_quote = in.eq('"') & immediately_follows(in.eq('\'), prev_backslash); // really_inline uint64_t follows(const uint64_t match, uint64_t &overflow) { const uint64_t result = match << 1 | overflow; overflow = match >> 63; return result; } // // Check if the current character follows a matching character, with possible "filler" between. // For example, this checks for empty curly braces, e.g. // // in.eq('}') & follows(in.eq('['), in.eq(' '), prev_empty_array) // { * } // really_inline uint64_t follows(const uint64_t match, const uint64_t filler, uint64_t &overflow ) { uint64_t follows_match = follows(match, overflow); uint64_t result; overflow |= add_overflow(follows_match, filler, &result); return result; } really_inline ErrorValues detect_errors_on_eof( uint64_t &unescaped_chars_error, const uint64_t prev_in_string) { if (prev_in_string) { return UNCLOSED_STRING; } if (unescaped_chars_error) { return UNESCAPED_CHARS; } return SUCCESS; } // // Return a mask of all string characters plus end quotes. // // prev_escaped is overflow saying whether the next character is escaped. // prev_in_string is overflow saying whether we're still in a string. // // Backslash sequences outside of quotes will be detected in stage 2. // really_inline uint64_t find_strings(const simd_input in, uint64_t &prev_escaped, uint64_t &prev_in_string) { const uint64_t backslash = in.eq('\\'); const uint64_t escaped = follows_odd_sequence_of(backslash, prev_escaped); const uint64_t quote = in.eq('"') & ~escaped; // compute_quote_mask returns start quote plus string contents. const uint64_t in_string = compute_quote_mask(quote) ^ prev_in_string; /* right shift of a signed value expected to be well-defined and standard * compliant as of C++20, * John Regher from Utah U. says this is fine code */ prev_in_string = static_cast(static_cast(in_string) >> 63); // Use ^ to turn the beginning quote off, and the end quote on. return in_string ^ quote; } really_inline uint64_t invalid_string_bytes(const uint64_t unescaped, const uint64_t quote_mask) { /* 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). * https://tools.ietf.org/html/rfc8259 */ return quote_mask & unescaped; } // // Determine which characters are *structural*: // - braces: [] and {} // - the start of primitives (123, true, false, null) // - the start of invalid non-whitespace (+, &, ture, UTF-8) // // Also detects value sequence errors: // - two values with no separator between ("hello" "world") // - separators with no values ([1,] [1,,]and [,2]) // // This method will find all of the above whether it is in a string or not. // // To reduce dependency on the expensive "what is in a string" computation, this method treats the // contents of a string the same as content outside. Errors and structurals inside the string or on // the trailing quote will need to be removed later when the correct string information is known. // really_inline uint64_t find_potential_structurals(const simd_input in, uint64_t &prev_primitive) { // These use SIMD so let's kick them off before running the regular 64-bit stuff ... uint64_t whitespace, op; find_whitespace_and_operators(in, whitespace, op); // Detect the start of a run of primitive characters. Includes numbers, booleans, and strings ("). // Everything except whitespace, braces, colon and comma. const uint64_t primitive = ~(op | whitespace); const uint64_t follows_primitive = follows(primitive, prev_primitive); const uint64_t start_primitive = primitive & ~follows_primitive; // Return final structurals return op | start_primitive; } static const size_t STEP_SIZE = 128; // // Find the important bits of JSON in a 128-byte chunk, and add them to : // // // // PERF NOTES: // We pipe 2 inputs through these stages: // 1. Load JSON into registers. This takes a long time and is highly parallelizable, so we load // 2 inputs' worth at once so that by the time step 2 is looking for them input, it's available. // 2. Scan the JSON for critical data: strings, primitives and operators. This is the critical path. // The output of step 1 depends entirely on this information. These functions don't quite use // up enough CPU: the second half of the functions is highly serial, only using 1 execution core // at a time. The second input's scans has some dependency on the first ones finishing it, but // they can make a lot of progress before they need that information. // 3. Step 1 doesn't use enough capacity, so we run some extra stuff while we're waiting for that // to finish: utf-8 checks and generating the output from the last iteration. // // The reason we run 2 inputs at a time, is steps 2 and 3 are *still* not enough to soak up all // available capacity with just one input. Running 2 at a time seems to give the CPU a good enough // workout. // really_inline void find_structural_bits_128( const uint8_t *buf, const size_t idx, uint32_t *&base_ptr, uint64_t &prev_escaped, uint64_t &prev_in_string, uint64_t &prev_primitive, uint64_t &prev_structurals, uint64_t &unescaped_chars_error, utf8_checker &utf8_state) { // // Load up all 128 bytes into SIMD registers // simd_input in_1(buf); simd_input in_2(buf+64); // // Find the strings and potential structurals (operators / primitives). // // This will include false structurals that are *inside* strings--we'll filter strings out // before we return. // uint64_t string_1 = find_strings(in_1, prev_escaped, prev_in_string); uint64_t structurals_1 = find_potential_structurals(in_1, prev_primitive); uint64_t string_2 = find_strings(in_2, prev_escaped, prev_in_string); uint64_t structurals_2 = find_potential_structurals(in_2, prev_primitive); // // Do miscellaneous work while the processor is busy calculating strings and structurals. // // After that, weed out structurals that are inside strings and find invalid string characters. // uint64_t unescaped_1 = in_1.lteq(0x1F); utf8_state.check_next_input(in_1); flatten_bits(base_ptr, idx, prev_structurals); // Output *last* iteration's structurals to ParsedJson prev_structurals = structurals_1 & ~string_1; unescaped_chars_error |= unescaped_1 & string_1; uint64_t unescaped_2 = in_2.lteq(0x1F); utf8_state.check_next_input(in_2); flatten_bits(base_ptr, idx+64, prev_structurals); // Output *last* iteration's structurals to ParsedJson prev_structurals = structurals_2 & ~string_2; unescaped_chars_error |= unescaped_2 & string_2; } int find_structural_bits(const uint8_t *buf, size_t len, simdjson::ParsedJson &pj) { if (unlikely(len > pj.byte_capacity)) { std::cerr << "Your ParsedJson object only supports documents up to " << pj.byte_capacity << " bytes but you are trying to process " << len << " bytes" << std::endl; return simdjson::CAPACITY; } uint32_t *base_ptr = pj.structural_indexes; utf8_checker utf8_state; // Whether the first character of the next iteration is escaped. uint64_t prev_escaped = 0ULL; // Whether the last iteration was still inside a string (all 1's = true, all 0's = false). uint64_t prev_in_string = 0ULL; // Whether the last character of the previous iteration is a primitive value character // (anything except whitespace, braces, comma or colon). uint64_t prev_primitive = 0ULL; // Mask of structural characters from the last iteration. // Kept around for performance reasons, so we can call flatten_bits to soak up some unused // CPU capacity while the next iteration is busy with an expensive clmul in compute_quote_mask. uint64_t structurals = 0; size_t lenminusstep = len < STEP_SIZE ? 0 : len - STEP_SIZE; size_t idx = 0; // Errors with unescaped characters in strings (ASCII codepoints < 0x20) uint64_t unescaped_chars_error = 0; for (; idx < lenminusstep; idx += STEP_SIZE) { find_structural_bits_128(&buf[idx], idx, base_ptr, prev_escaped, prev_in_string, prev_primitive, structurals, unescaped_chars_error, utf8_state); } /* If we have a final chunk of less than 64 bytes, pad it to 64 with * spaces before processing it (otherwise, we risk invalidating the UTF-8 * checks). */ if (likely(idx < len)) { uint8_t tmp_buf[STEP_SIZE]; memset(tmp_buf, 0x20, STEP_SIZE); memcpy(tmp_buf, buf + idx, len - idx); find_structural_bits_128(&tmp_buf[0], idx, base_ptr, prev_escaped, prev_in_string, prev_primitive, structurals, unescaped_chars_error, utf8_state); idx += STEP_SIZE; } /* finally, flatten out the remaining structurals from the last iteration */ flatten_bits(base_ptr, idx, structurals); simdjson::ErrorValues error = detect_errors_on_eof(unescaped_chars_error, prev_in_string); if (unlikely(error != simdjson::SUCCESS)) { return error; } pj.n_structural_indexes = base_ptr - pj.structural_indexes; /* a valid JSON file cannot have zero structural indexes - we should have * found something */ if (unlikely(pj.n_structural_indexes == 0u)) { return simdjson::EMPTY; } if (unlikely(pj.structural_indexes[pj.n_structural_indexes - 1] > len)) { return simdjson::UNEXPECTED_ERROR; } if (len != pj.structural_indexes[pj.n_structural_indexes - 1]) { /* the string might not be NULL terminated, but we add a virtual NULL * ending character. */ pj.structural_indexes[pj.n_structural_indexes++] = len; } /* make it safe to dereference one beyond this array */ pj.structural_indexes[pj.n_structural_indexes] = 0; return utf8_state.errors(); } } // namespace haswell UNTARGET_REGION TARGET_HASWELL namespace simdjson { template <> int find_structural_bits(const uint8_t *buf, size_t len, simdjson::ParsedJson &pj) { return haswell::find_structural_bits(buf, len, pj); } } // namespace simdjson UNTARGET_REGION #endif // IS_X86_64 #endif // SIMDJSON_HASWELL_STAGE1_FIND_MARKS_H /* end file src/haswell/stage1_find_marks.h */ /* begin file src/westmere/stage1_find_marks.h */ #ifndef SIMDJSON_WESTMERE_STAGE1_FIND_MARKS_H #define SIMDJSON_WESTMERE_STAGE1_FIND_MARKS_H #ifdef IS_X86_64 TARGET_WESTMERE namespace simdjson::westmere { really_inline uint64_t compute_quote_mask(const uint64_t quote_bits) { return _mm_cvtsi128_si64(_mm_clmulepi64_si128( _mm_set_epi64x(0ULL, quote_bits), _mm_set1_epi8(0xFFu), 0)); } really_inline void find_whitespace_and_operators( const simd_input in, uint64_t &whitespace, uint64_t &op) { const __m128i operator_table = _mm_setr_epi8(44, 125, 0, 0, 0xc0u, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58, 123); const __m128i white_table = _mm_setr_epi8(32, 100, 100, 100, 17, 100, 113, 2, 100, 9, 10, 112, 100, 13, 100, 100); const __m128i op_offset = _mm_set1_epi8(0xd4u); const __m128i op_mask = _mm_set1_epi8(32); whitespace = in.map([&](auto _in) { return _mm_cmpeq_epi8(_in, _mm_shuffle_epi8(white_table, _in)); }).to_bitmask(); op = in.map([&](auto _in) { const __m128i r1 = _mm_add_epi8(op_offset, _in); const __m128i r2 = _mm_or_si128(_in, op_mask); const __m128i r3 = _mm_shuffle_epi8(operator_table, r1); return _mm_cmpeq_epi8(r2, r3); }).to_bitmask(); } // This file contains a non-architecture-specific version of "flatten" used in stage1. // It is intended to be included multiple times and compiled multiple times // We assume the file in which it is include already includes // "simdjson/stage1_find_marks.h" (this simplifies amalgation) #ifdef SIMDJSON_NAIVE_FLATTEN // useful for benchmarking // This is just a naive implementation. It should be normally // disable, but can be used for research purposes to compare // again our optimized version. really_inline void flatten_bits(uint32_t *base_ptr, uint32_t &base, uint32_t idx, uint64_t bits) { uint32_t *out_ptr = base_ptr + base; idx -= 64; while (bits != 0) { out_ptr[0] = idx + trailing_zeroes(bits); bits = bits & (bits - 1); out_ptr++; } base = (out_ptr - base_ptr); } #else // SIMDJSON_NAIVE_FLATTEN // flatten out values in 'bits' assuming that they are are to have values of idx // plus their position in the bitvector, and store these indexes at // base_ptr[base] incrementing base as we go // will potentially store extra values beyond end of valid bits, so base_ptr // needs to be large enough to handle this really_inline void flatten_bits(uint32_t *&base_ptr, uint32_t idx, uint64_t bits) { // In some instances, the next branch is expensive because it is mispredicted. // Unfortunately, in other cases, // it helps tremendously. if (bits == 0) return; uint32_t cnt = hamming(bits); idx -= 64; // Do the first 8 all together for (int i=0; i<8; i++) { base_ptr[i] = idx + trailing_zeroes(bits); bits = bits & (bits - 1); } // Do the next 8 all together (we hope in most cases it won't happen at all // and the branch is easily predicted). if (unlikely(cnt > 8)) { for (int i=8; i<16; i++) { base_ptr[i] = idx + trailing_zeroes(bits); bits = bits & (bits - 1); } // Most files don't have 16+ structurals per block, so we take several basically guaranteed // branch mispredictions here. 16+ structurals per block means either punctuation ({} [] , :) // or the start of a value ("abc" true 123) every 4 characters. if (unlikely(cnt > 16)) { uint32_t i = 16; do { base_ptr[i] = idx + trailing_zeroes(bits); bits = bits & (bits - 1); i++; } while (i < cnt); } } base_ptr += cnt; } #endif // SIMDJSON_NAIVE_FLATTEN // This file contains the common code every implementation uses in stage1 // It is intended to be included multiple times and compiled multiple times // We assume the file in which it is included already includes // "simdjson/stage1_find_marks.h" (this simplifies amalgation) // return a bitvector indicating where we have characters that end an odd-length // sequence of backslashes (and thus change the behavior of the next character // to follow). A even-length sequence of backslashes, and, for that matter, the // largest even-length prefix of our odd-length sequence of backslashes, simply // modify the behavior of the backslashes themselves. // We also update the prev_iter_ends_odd_backslash reference parameter to // indicate whether we end an iteration on an odd-length sequence of // backslashes, which modifies our subsequent search for odd-length // sequences of backslashes in an obvious way. really_inline uint64_t follows_odd_sequence_of(const uint64_t match, uint64_t &overflow) { const uint64_t even_bits = 0x5555555555555555ULL; const uint64_t odd_bits = ~even_bits; uint64_t start_edges = match & ~(match << 1); /* flip lowest if we have an odd-length run at the end of the prior * iteration */ uint64_t even_start_mask = even_bits ^ overflow; uint64_t even_starts = start_edges & even_start_mask; uint64_t odd_starts = start_edges & ~even_start_mask; uint64_t even_carries = match + even_starts; uint64_t odd_carries; /* must record the carry-out of our odd-carries out of bit 63; this * indicates whether the sense of any edge going to the next iteration * should be flipped */ bool new_overflow = add_overflow(match, odd_starts, &odd_carries); odd_carries |= overflow; /* push in bit zero as a * potential end if we had an * odd-numbered run at the * end of the previous * iteration */ overflow = new_overflow ? 0x1ULL : 0x0ULL; uint64_t even_carry_ends = even_carries & ~match; uint64_t odd_carry_ends = odd_carries & ~match; uint64_t even_start_odd_end = even_carry_ends & odd_bits; uint64_t odd_start_even_end = odd_carry_ends & even_bits; uint64_t odd_ends = even_start_odd_end | odd_start_even_end; return odd_ends; } // // Check if the current character immediately follows a matching character. // // For example, this checks for quotes with backslashes in front of them: // // const uint64_t backslashed_quote = in.eq('"') & immediately_follows(in.eq('\'), prev_backslash); // really_inline uint64_t follows(const uint64_t match, uint64_t &overflow) { const uint64_t result = match << 1 | overflow; overflow = match >> 63; return result; } // // Check if the current character follows a matching character, with possible "filler" between. // For example, this checks for empty curly braces, e.g. // // in.eq('}') & follows(in.eq('['), in.eq(' '), prev_empty_array) // { * } // really_inline uint64_t follows(const uint64_t match, const uint64_t filler, uint64_t &overflow ) { uint64_t follows_match = follows(match, overflow); uint64_t result; overflow |= add_overflow(follows_match, filler, &result); return result; } really_inline ErrorValues detect_errors_on_eof( uint64_t &unescaped_chars_error, const uint64_t prev_in_string) { if (prev_in_string) { return UNCLOSED_STRING; } if (unescaped_chars_error) { return UNESCAPED_CHARS; } return SUCCESS; } // // Return a mask of all string characters plus end quotes. // // prev_escaped is overflow saying whether the next character is escaped. // prev_in_string is overflow saying whether we're still in a string. // // Backslash sequences outside of quotes will be detected in stage 2. // really_inline uint64_t find_strings(const simd_input in, uint64_t &prev_escaped, uint64_t &prev_in_string) { const uint64_t backslash = in.eq('\\'); const uint64_t escaped = follows_odd_sequence_of(backslash, prev_escaped); const uint64_t quote = in.eq('"') & ~escaped; // compute_quote_mask returns start quote plus string contents. const uint64_t in_string = compute_quote_mask(quote) ^ prev_in_string; /* right shift of a signed value expected to be well-defined and standard * compliant as of C++20, * John Regher from Utah U. says this is fine code */ prev_in_string = static_cast(static_cast(in_string) >> 63); // Use ^ to turn the beginning quote off, and the end quote on. return in_string ^ quote; } really_inline uint64_t invalid_string_bytes(const uint64_t unescaped, const uint64_t quote_mask) { /* 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). * https://tools.ietf.org/html/rfc8259 */ return quote_mask & unescaped; } // // Determine which characters are *structural*: // - braces: [] and {} // - the start of primitives (123, true, false, null) // - the start of invalid non-whitespace (+, &, ture, UTF-8) // // Also detects value sequence errors: // - two values with no separator between ("hello" "world") // - separators with no values ([1,] [1,,]and [,2]) // // This method will find all of the above whether it is in a string or not. // // To reduce dependency on the expensive "what is in a string" computation, this method treats the // contents of a string the same as content outside. Errors and structurals inside the string or on // the trailing quote will need to be removed later when the correct string information is known. // really_inline uint64_t find_potential_structurals(const simd_input in, uint64_t &prev_primitive) { // These use SIMD so let's kick them off before running the regular 64-bit stuff ... uint64_t whitespace, op; find_whitespace_and_operators(in, whitespace, op); // Detect the start of a run of primitive characters. Includes numbers, booleans, and strings ("). // Everything except whitespace, braces, colon and comma. const uint64_t primitive = ~(op | whitespace); const uint64_t follows_primitive = follows(primitive, prev_primitive); const uint64_t start_primitive = primitive & ~follows_primitive; // Return final structurals return op | start_primitive; } static const size_t STEP_SIZE = 128; // // Find the important bits of JSON in a 128-byte chunk, and add them to : // // // // PERF NOTES: // We pipe 2 inputs through these stages: // 1. Load JSON into registers. This takes a long time and is highly parallelizable, so we load // 2 inputs' worth at once so that by the time step 2 is looking for them input, it's available. // 2. Scan the JSON for critical data: strings, primitives and operators. This is the critical path. // The output of step 1 depends entirely on this information. These functions don't quite use // up enough CPU: the second half of the functions is highly serial, only using 1 execution core // at a time. The second input's scans has some dependency on the first ones finishing it, but // they can make a lot of progress before they need that information. // 3. Step 1 doesn't use enough capacity, so we run some extra stuff while we're waiting for that // to finish: utf-8 checks and generating the output from the last iteration. // // The reason we run 2 inputs at a time, is steps 2 and 3 are *still* not enough to soak up all // available capacity with just one input. Running 2 at a time seems to give the CPU a good enough // workout. // really_inline void find_structural_bits_128( const uint8_t *buf, const size_t idx, uint32_t *&base_ptr, uint64_t &prev_escaped, uint64_t &prev_in_string, uint64_t &prev_primitive, uint64_t &prev_structurals, uint64_t &unescaped_chars_error, utf8_checker &utf8_state) { // // Load up all 128 bytes into SIMD registers // simd_input in_1(buf); simd_input in_2(buf+64); // // Find the strings and potential structurals (operators / primitives). // // This will include false structurals that are *inside* strings--we'll filter strings out // before we return. // uint64_t string_1 = find_strings(in_1, prev_escaped, prev_in_string); uint64_t structurals_1 = find_potential_structurals(in_1, prev_primitive); uint64_t string_2 = find_strings(in_2, prev_escaped, prev_in_string); uint64_t structurals_2 = find_potential_structurals(in_2, prev_primitive); // // Do miscellaneous work while the processor is busy calculating strings and structurals. // // After that, weed out structurals that are inside strings and find invalid string characters. // uint64_t unescaped_1 = in_1.lteq(0x1F); utf8_state.check_next_input(in_1); flatten_bits(base_ptr, idx, prev_structurals); // Output *last* iteration's structurals to ParsedJson prev_structurals = structurals_1 & ~string_1; unescaped_chars_error |= unescaped_1 & string_1; uint64_t unescaped_2 = in_2.lteq(0x1F); utf8_state.check_next_input(in_2); flatten_bits(base_ptr, idx+64, prev_structurals); // Output *last* iteration's structurals to ParsedJson prev_structurals = structurals_2 & ~string_2; unescaped_chars_error |= unescaped_2 & string_2; } int find_structural_bits(const uint8_t *buf, size_t len, simdjson::ParsedJson &pj) { if (unlikely(len > pj.byte_capacity)) { std::cerr << "Your ParsedJson object only supports documents up to " << pj.byte_capacity << " bytes but you are trying to process " << len << " bytes" << std::endl; return simdjson::CAPACITY; } uint32_t *base_ptr = pj.structural_indexes; utf8_checker utf8_state; // Whether the first character of the next iteration is escaped. uint64_t prev_escaped = 0ULL; // Whether the last iteration was still inside a string (all 1's = true, all 0's = false). uint64_t prev_in_string = 0ULL; // Whether the last character of the previous iteration is a primitive value character // (anything except whitespace, braces, comma or colon). uint64_t prev_primitive = 0ULL; // Mask of structural characters from the last iteration. // Kept around for performance reasons, so we can call flatten_bits to soak up some unused // CPU capacity while the next iteration is busy with an expensive clmul in compute_quote_mask. uint64_t structurals = 0; size_t lenminusstep = len < STEP_SIZE ? 0 : len - STEP_SIZE; size_t idx = 0; // Errors with unescaped characters in strings (ASCII codepoints < 0x20) uint64_t unescaped_chars_error = 0; for (; idx < lenminusstep; idx += STEP_SIZE) { find_structural_bits_128(&buf[idx], idx, base_ptr, prev_escaped, prev_in_string, prev_primitive, structurals, unescaped_chars_error, utf8_state); } /* If we have a final chunk of less than 64 bytes, pad it to 64 with * spaces before processing it (otherwise, we risk invalidating the UTF-8 * checks). */ if (likely(idx < len)) { uint8_t tmp_buf[STEP_SIZE]; memset(tmp_buf, 0x20, STEP_SIZE); memcpy(tmp_buf, buf + idx, len - idx); find_structural_bits_128(&tmp_buf[0], idx, base_ptr, prev_escaped, prev_in_string, prev_primitive, structurals, unescaped_chars_error, utf8_state); idx += STEP_SIZE; } /* finally, flatten out the remaining structurals from the last iteration */ flatten_bits(base_ptr, idx, structurals); simdjson::ErrorValues error = detect_errors_on_eof(unescaped_chars_error, prev_in_string); if (unlikely(error != simdjson::SUCCESS)) { return error; } pj.n_structural_indexes = base_ptr - pj.structural_indexes; /* a valid JSON file cannot have zero structural indexes - we should have * found something */ if (unlikely(pj.n_structural_indexes == 0u)) { return simdjson::EMPTY; } if (unlikely(pj.structural_indexes[pj.n_structural_indexes - 1] > len)) { return simdjson::UNEXPECTED_ERROR; } if (len != pj.structural_indexes[pj.n_structural_indexes - 1]) { /* the string might not be NULL terminated, but we add a virtual NULL * ending character. */ pj.structural_indexes[pj.n_structural_indexes++] = len; } /* make it safe to dereference one beyond this array */ pj.structural_indexes[pj.n_structural_indexes] = 0; return utf8_state.errors(); } } // namespace westmere UNTARGET_REGION TARGET_WESTMERE namespace simdjson { template <> int find_structural_bits(const uint8_t *buf, size_t len, simdjson::ParsedJson &pj) { return westmere::find_structural_bits(buf, len, pj); } } // namespace simdjson UNTARGET_REGION #endif // IS_X86_64 #endif // SIMDJSON_WESTMERE_STAGE1_FIND_MARKS_H /* end file src/westmere/stage1_find_marks.h */ /* begin file src/stage1_find_marks.cpp */ namespace { // for when clmul is unavailable [[maybe_unused]] uint64_t portable_compute_quote_mask(uint64_t quote_bits) { uint64_t quote_mask = quote_bits ^ (quote_bits << 1); quote_mask = quote_mask ^ (quote_mask << 2); quote_mask = quote_mask ^ (quote_mask << 4); quote_mask = quote_mask ^ (quote_mask << 8); quote_mask = quote_mask ^ (quote_mask << 16); quote_mask = quote_mask ^ (quote_mask << 32); return quote_mask; } } // namespace /* end file src/stage1_find_marks.cpp */ /* begin file src/arm64/stringparsing.h */ #ifndef SIMDJSON_ARM64_STRINGPARSING_H #define SIMDJSON_ARM64_STRINGPARSING_H #ifdef IS_ARM64 #ifdef JSON_TEST_STRINGS void found_string(const uint8_t *buf, const uint8_t *parsed_begin, const uint8_t *parsed_end); void found_bad_string(const uint8_t *buf); #endif namespace simdjson::arm64 { // Holds backslashes and quotes locations. struct parse_string_helper { uint32_t bs_bits; uint32_t quote_bits; really_inline uint32_t bytes_processed() const { return sizeof(uint8x16_t); } }; really_inline parse_string_helper find_bs_bits_and_quote_bits(const uint8_t *src, uint8_t *dst) { // this can read up to 31 bytes beyond the buffer size, but we require // SIMDJSON_PADDING of padding static_assert(2 * sizeof(uint8x16_t) - 1 <= SIMDJSON_PADDING); uint8x16_t v0 = vld1q_u8(src); uint8x16_t v1 = vld1q_u8(src + 16); vst1q_u8(dst, v0); vst1q_u8(dst + 16, v1); uint8x16_t bs_mask = vmovq_n_u8('\\'); uint8x16_t qt_mask = vmovq_n_u8('"'); const uint8x16_t bit_mask = {0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80}; uint8x16_t cmp_bs_0 = vceqq_u8(v0, bs_mask); uint8x16_t cmp_bs_1 = vceqq_u8(v1, bs_mask); uint8x16_t cmp_qt_0 = vceqq_u8(v0, qt_mask); uint8x16_t cmp_qt_1 = vceqq_u8(v1, qt_mask); cmp_bs_0 = vandq_u8(cmp_bs_0, bit_mask); cmp_bs_1 = vandq_u8(cmp_bs_1, bit_mask); cmp_qt_0 = vandq_u8(cmp_qt_0, bit_mask); cmp_qt_1 = vandq_u8(cmp_qt_1, bit_mask); uint8x16_t sum0 = vpaddq_u8(cmp_bs_0, cmp_bs_1); uint8x16_t sum1 = vpaddq_u8(cmp_qt_0, cmp_qt_1); sum0 = vpaddq_u8(sum0, sum1); sum0 = vpaddq_u8(sum0, sum0); return { vgetq_lane_u32(vreinterpretq_u32_u8(sum0), 0), // bs_bits vgetq_lane_u32(vreinterpretq_u32_u8(sum0), 1) // quote_bits }; } // This file contains the common code every implementation uses // It is intended to be included multiple times and compiled multiple times // We assume the file in which it is include already includes // "stringparsing.h" (this simplifies amalgation) // begin copypasta // These chars yield themselves: " \ / // b -> backspace, f -> formfeed, n -> newline, r -> cr, t -> horizontal tab // u not handled in this table as it's complex static const uint8_t escape_map[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x0. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2f, 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, // 0x4. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x5c, 0, 0, 0, // 0x5. 0, 0, 0x08, 0, 0, 0, 0x0c, 0, 0, 0, 0, 0, 0, 0, 0x0a, 0, // 0x6. 0, 0, 0x0d, 0, 0x09, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x7. 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, }; // handle a unicode codepoint // write appropriate values into dest // src will advance 6 bytes or 12 bytes // dest will advance a variable amount (return via pointer) // return true if the unicode codepoint was valid // We work in little-endian then swap at write time WARN_UNUSED really_inline bool handle_unicode_codepoint(const uint8_t **src_ptr, uint8_t **dst_ptr) { // hex_to_u32_nocheck fills high 16 bits of the return value with 1s if the // conversion isn't valid; we defer the check for this to inside the // multilingual plane check uint32_t code_point = hex_to_u32_nocheck(*src_ptr + 2); *src_ptr += 6; // check for low surrogate for characters outside the Basic // Multilingual Plane. if (code_point >= 0xd800 && code_point < 0xdc00) { if (((*src_ptr)[0] != '\\') || (*src_ptr)[1] != 'u') { return false; } uint32_t code_point_2 = hex_to_u32_nocheck(*src_ptr + 2); // if the first code point is invalid we will get here, as we will go past // the check for being outside the Basic Multilingual plane. If we don't // find a \u immediately afterwards we fail out anyhow, but if we do, // this check catches both the case of the first code point being invalid // or the second code point being invalid. if ((code_point | code_point_2) >> 16) { return false; } code_point = (((code_point - 0xd800) << 10) | (code_point_2 - 0xdc00)) + 0x10000; *src_ptr += 6; } size_t offset = codepoint_to_utf8(code_point, *dst_ptr); *dst_ptr += offset; return offset > 0; } WARN_UNUSED really_inline bool parse_string(UNUSED const uint8_t *buf, UNUSED size_t len, ParsedJson &pj, UNUSED const uint32_t depth, UNUSED uint32_t offset) { pj.write_tape(pj.current_string_buf_loc - pj.string_buf, '"'); const uint8_t *src = &buf[offset + 1]; /* we know that buf at offset is a " */ uint8_t *dst = pj.current_string_buf_loc + sizeof(uint32_t); const uint8_t *const start_of_string = dst; while (1) { parse_string_helper helper = find_bs_bits_and_quote_bits(src, dst); if (((helper.bs_bits - 1) & helper.quote_bits) != 0) { /* we encountered quotes first. Move dst to point to quotes and exit */ /* find out where the quote is... */ uint32_t quote_dist = trailing_zeroes(helper.quote_bits); /* NULL termination is still handy if you expect all your strings to * be NULL terminated? */ /* It comes at a small cost */ dst[quote_dist] = 0; uint32_t str_length = (dst - start_of_string) + quote_dist; memcpy(pj.current_string_buf_loc, &str_length, sizeof(uint32_t)); /***************************** * 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 */ pj.current_string_buf_loc = dst + quote_dist + 1; return true; } if (((helper.quote_bits - 1) & helper.bs_bits) != 0) { /* find out where the backspace is */ uint32_t bs_dist = trailing_zeroes(helper.bs_bits); 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)) { return false; } } 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 false; /* 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 += helper.bytes_processed(); dst += helper.bytes_processed(); } } /* can't be reached */ return true; } } // namespace simdjson::amd64 #endif // IS_ARM64 #endif /* end file src/arm64/stringparsing.h */ /* begin file src/haswell/stringparsing.h */ #ifndef SIMDJSON_HASWELL_STRINGPARSING_H #define SIMDJSON_HASWELL_STRINGPARSING_H #ifdef IS_X86_64 #ifdef JSON_TEST_STRINGS void found_string(const uint8_t *buf, const uint8_t *parsed_begin, const uint8_t *parsed_end); void found_bad_string(const uint8_t *buf); #endif TARGET_HASWELL namespace simdjson::haswell { // Holds backslashes and quotes locations. struct parse_string_helper { uint32_t bs_bits; uint32_t quote_bits; really_inline uint32_t bytes_processed() const { return sizeof(__m256i); } }; really_inline parse_string_helper find_bs_bits_and_quote_bits(const uint8_t *src, uint8_t *dst) { // this can read up to 31 bytes beyond the buffer size, but we require // SIMDJSON_PADDING of padding static_assert(sizeof(__m256i) - 1 <= SIMDJSON_PADDING); __m256i v = _mm256_loadu_si256(reinterpret_cast(src)); // store to dest unconditionally - we can overwrite the bits we don't like // later _mm256_storeu_si256(reinterpret_cast<__m256i *>(dst), v); auto quote_mask = _mm256_cmpeq_epi8(v, _mm256_set1_epi8('"')); return { static_cast(_mm256_movemask_epi8( _mm256_cmpeq_epi8(v, _mm256_set1_epi8('\\')))), // bs_bits static_cast(_mm256_movemask_epi8(quote_mask)) // quote_bits }; } // This file contains the common code every implementation uses // It is intended to be included multiple times and compiled multiple times // We assume the file in which it is include already includes // "stringparsing.h" (this simplifies amalgation) // begin copypasta // These chars yield themselves: " \ / // b -> backspace, f -> formfeed, n -> newline, r -> cr, t -> horizontal tab // u not handled in this table as it's complex static const uint8_t escape_map[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x0. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2f, 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, // 0x4. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x5c, 0, 0, 0, // 0x5. 0, 0, 0x08, 0, 0, 0, 0x0c, 0, 0, 0, 0, 0, 0, 0, 0x0a, 0, // 0x6. 0, 0, 0x0d, 0, 0x09, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x7. 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, }; // handle a unicode codepoint // write appropriate values into dest // src will advance 6 bytes or 12 bytes // dest will advance a variable amount (return via pointer) // return true if the unicode codepoint was valid // We work in little-endian then swap at write time WARN_UNUSED really_inline bool handle_unicode_codepoint(const uint8_t **src_ptr, uint8_t **dst_ptr) { // hex_to_u32_nocheck fills high 16 bits of the return value with 1s if the // conversion isn't valid; we defer the check for this to inside the // multilingual plane check uint32_t code_point = hex_to_u32_nocheck(*src_ptr + 2); *src_ptr += 6; // check for low surrogate for characters outside the Basic // Multilingual Plane. if (code_point >= 0xd800 && code_point < 0xdc00) { if (((*src_ptr)[0] != '\\') || (*src_ptr)[1] != 'u') { return false; } uint32_t code_point_2 = hex_to_u32_nocheck(*src_ptr + 2); // if the first code point is invalid we will get here, as we will go past // the check for being outside the Basic Multilingual plane. If we don't // find a \u immediately afterwards we fail out anyhow, but if we do, // this check catches both the case of the first code point being invalid // or the second code point being invalid. if ((code_point | code_point_2) >> 16) { return false; } code_point = (((code_point - 0xd800) << 10) | (code_point_2 - 0xdc00)) + 0x10000; *src_ptr += 6; } size_t offset = codepoint_to_utf8(code_point, *dst_ptr); *dst_ptr += offset; return offset > 0; } WARN_UNUSED really_inline bool parse_string(UNUSED const uint8_t *buf, UNUSED size_t len, ParsedJson &pj, UNUSED const uint32_t depth, UNUSED uint32_t offset) { pj.write_tape(pj.current_string_buf_loc - pj.string_buf, '"'); const uint8_t *src = &buf[offset + 1]; /* we know that buf at offset is a " */ uint8_t *dst = pj.current_string_buf_loc + sizeof(uint32_t); const uint8_t *const start_of_string = dst; while (1) { parse_string_helper helper = find_bs_bits_and_quote_bits(src, dst); if (((helper.bs_bits - 1) & helper.quote_bits) != 0) { /* we encountered quotes first. Move dst to point to quotes and exit */ /* find out where the quote is... */ uint32_t quote_dist = trailing_zeroes(helper.quote_bits); /* NULL termination is still handy if you expect all your strings to * be NULL terminated? */ /* It comes at a small cost */ dst[quote_dist] = 0; uint32_t str_length = (dst - start_of_string) + quote_dist; memcpy(pj.current_string_buf_loc, &str_length, sizeof(uint32_t)); /***************************** * 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 */ pj.current_string_buf_loc = dst + quote_dist + 1; return true; } if (((helper.quote_bits - 1) & helper.bs_bits) != 0) { /* find out where the backspace is */ uint32_t bs_dist = trailing_zeroes(helper.bs_bits); 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)) { return false; } } 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 false; /* 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 += helper.bytes_processed(); dst += helper.bytes_processed(); } } /* can't be reached */ return true; } } // namespace simdjson::haswell UNTARGET_REGION #endif // IS_X86_64 #endif /* end file src/haswell/stringparsing.h */ /* begin file src/westmere/stringparsing.h */ #ifndef SIMDJSON_WESTMERE_STRINGPARSING_H #define SIMDJSON_WESTMERE_STRINGPARSING_H #ifdef IS_X86_64 #ifdef JSON_TEST_STRINGS void found_string(const uint8_t *buf, const uint8_t *parsed_begin, const uint8_t *parsed_end); void found_bad_string(const uint8_t *buf); #endif TARGET_WESTMERE namespace simdjson::westmere { // Holds backslashes and quotes locations. struct parse_string_helper { uint32_t bs_bits; uint32_t quote_bits; really_inline uint32_t bytes_processed() const { return sizeof(__m128i); } }; really_inline parse_string_helper find_bs_bits_and_quote_bits(const uint8_t *src, uint8_t *dst) { // this can read up to 31 bytes beyond the buffer size, but we require // SIMDJSON_PADDING of padding __m128i v = _mm_loadu_si128(reinterpret_cast(src)); // store to dest unconditionally - we can overwrite the bits we don't like // later _mm_storeu_si128(reinterpret_cast<__m128i *>(dst), v); auto quote_mask = _mm_cmpeq_epi8(v, _mm_set1_epi8('"')); return { static_cast( _mm_movemask_epi8(_mm_cmpeq_epi8(v, _mm_set1_epi8('\\')))), // bs_bits static_cast(_mm_movemask_epi8(quote_mask)) // quote_bits }; } // This file contains the common code every implementation uses // It is intended to be included multiple times and compiled multiple times // We assume the file in which it is include already includes // "stringparsing.h" (this simplifies amalgation) // begin copypasta // These chars yield themselves: " \ / // b -> backspace, f -> formfeed, n -> newline, r -> cr, t -> horizontal tab // u not handled in this table as it's complex static const uint8_t escape_map[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x0. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2f, 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, // 0x4. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x5c, 0, 0, 0, // 0x5. 0, 0, 0x08, 0, 0, 0, 0x0c, 0, 0, 0, 0, 0, 0, 0, 0x0a, 0, // 0x6. 0, 0, 0x0d, 0, 0x09, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x7. 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, }; // handle a unicode codepoint // write appropriate values into dest // src will advance 6 bytes or 12 bytes // dest will advance a variable amount (return via pointer) // return true if the unicode codepoint was valid // We work in little-endian then swap at write time WARN_UNUSED really_inline bool handle_unicode_codepoint(const uint8_t **src_ptr, uint8_t **dst_ptr) { // hex_to_u32_nocheck fills high 16 bits of the return value with 1s if the // conversion isn't valid; we defer the check for this to inside the // multilingual plane check uint32_t code_point = hex_to_u32_nocheck(*src_ptr + 2); *src_ptr += 6; // check for low surrogate for characters outside the Basic // Multilingual Plane. if (code_point >= 0xd800 && code_point < 0xdc00) { if (((*src_ptr)[0] != '\\') || (*src_ptr)[1] != 'u') { return false; } uint32_t code_point_2 = hex_to_u32_nocheck(*src_ptr + 2); // if the first code point is invalid we will get here, as we will go past // the check for being outside the Basic Multilingual plane. If we don't // find a \u immediately afterwards we fail out anyhow, but if we do, // this check catches both the case of the first code point being invalid // or the second code point being invalid. if ((code_point | code_point_2) >> 16) { return false; } code_point = (((code_point - 0xd800) << 10) | (code_point_2 - 0xdc00)) + 0x10000; *src_ptr += 6; } size_t offset = codepoint_to_utf8(code_point, *dst_ptr); *dst_ptr += offset; return offset > 0; } WARN_UNUSED really_inline bool parse_string(UNUSED const uint8_t *buf, UNUSED size_t len, ParsedJson &pj, UNUSED const uint32_t depth, UNUSED uint32_t offset) { pj.write_tape(pj.current_string_buf_loc - pj.string_buf, '"'); const uint8_t *src = &buf[offset + 1]; /* we know that buf at offset is a " */ uint8_t *dst = pj.current_string_buf_loc + sizeof(uint32_t); const uint8_t *const start_of_string = dst; while (1) { parse_string_helper helper = find_bs_bits_and_quote_bits(src, dst); if (((helper.bs_bits - 1) & helper.quote_bits) != 0) { /* we encountered quotes first. Move dst to point to quotes and exit */ /* find out where the quote is... */ uint32_t quote_dist = trailing_zeroes(helper.quote_bits); /* NULL termination is still handy if you expect all your strings to * be NULL terminated? */ /* It comes at a small cost */ dst[quote_dist] = 0; uint32_t str_length = (dst - start_of_string) + quote_dist; memcpy(pj.current_string_buf_loc, &str_length, sizeof(uint32_t)); /***************************** * 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 */ pj.current_string_buf_loc = dst + quote_dist + 1; return true; } if (((helper.quote_bits - 1) & helper.bs_bits) != 0) { /* find out where the backspace is */ uint32_t bs_dist = trailing_zeroes(helper.bs_bits); 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)) { return false; } } 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 false; /* 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 += helper.bytes_processed(); dst += helper.bytes_processed(); } } /* can't be reached */ return true; } } // namespace simdjson::westmere UNTARGET_REGION #endif // IS_X86_64 #endif /* end file src/westmere/stringparsing.h */ /* begin file src/arm64/stage2_build_tape.h */ #ifndef SIMDJSON_ARM64_STAGE2_BUILD_TAPE_H #define SIMDJSON_ARM64_STAGE2_BUILD_TAPE_H #ifdef IS_ARM64 namespace simdjson::arm64 { // This file contains the common code every implementation uses for stage2 // It is intended to be included multiple times and compiled multiple times // We assume the file in which it is include already includes // "simdjson/stage2_build_tape.h" (this simplifies amalgation) // this macro reads the next structural character, updating idx, i and c. #define UPDATE_CHAR() \ { \ idx = pj.structural_indexes[i++]; \ c = buf[idx]; \ } #ifdef SIMDJSON_USE_COMPUTED_GOTO #define SET_GOTO_ARRAY_CONTINUE() pj.ret_address[depth] = &&array_continue; #define SET_GOTO_OBJECT_CONTINUE() pj.ret_address[depth] = &&object_continue; #define SET_GOTO_START_CONTINUE() pj.ret_address[depth] = &&start_continue; #define GOTO_CONTINUE() goto *pj.ret_address[depth]; #else #define SET_GOTO_ARRAY_CONTINUE() pj.ret_address[depth] = 'a'; #define SET_GOTO_OBJECT_CONTINUE() pj.ret_address[depth] = 'o'; #define SET_GOTO_START_CONTINUE() pj.ret_address[depth] = 's'; #define GOTO_CONTINUE() \ { \ if (pj.ret_address[depth] == 'a') { \ goto array_continue; \ } else if (pj.ret_address[depth] == 'o') { \ goto object_continue; \ } else { \ goto start_continue; \ } \ } #endif /************ * The JSON is parsed to a tape, see the accompanying tape.md file * for documentation. ***********/ WARN_UNUSED int unified_machine(const uint8_t *buf, size_t len, ParsedJson &pj) { uint32_t i = 0; /* index of the structural character (0,1,2,3...) */ uint32_t idx; /* location of the structural character in the input (buf) */ uint8_t c; /* used to track the (structural) character we are looking at, updated */ /* by UPDATE_CHAR macro */ uint32_t depth = 0; /* could have an arbitrary starting depth */ pj.init(); /* sets is_valid to false */ if (pj.byte_capacity < len) { pj.error_code = simdjson::CAPACITY; return pj.error_code; } /*//////////////////////////// START STATE ///////////////////////////// */ SET_GOTO_START_CONTINUE() pj.containing_scope_offset[depth] = pj.get_current_loc(); pj.write_tape(0, 'r'); /* r for root, 0 is going to get overwritten */ /* the root is used, if nothing else, to capture the size of the tape */ depth++; /* everything starts at depth = 1, depth = 0 is just for the root, the root may contain an object, an array or something else. */ if (depth >= pj.depth_capacity) { goto fail; } UPDATE_CHAR(); switch (c) { case '{': pj.containing_scope_offset[depth] = pj.get_current_loc(); SET_GOTO_START_CONTINUE(); depth++; if (depth >= pj.depth_capacity) { goto fail; } pj.write_tape( 0, c); /* strangely, moving this to object_begin slows things down */ goto object_begin; case '[': pj.containing_scope_offset[depth] = pj.get_current_loc(); SET_GOTO_START_CONTINUE(); depth++; if (depth >= pj.depth_capacity) { goto fail; } pj.write_tape(0, c); goto array_begin; /* #define SIMDJSON_ALLOWANYTHINGINROOT * A JSON text is a serialized value. Note that certain previous * specifications of JSON constrained a JSON text to be an object or an * array. Implementations that generate only objects or arrays where a * JSON text is called for will be interoperable in the sense that all * implementations will accept these as conforming JSON texts. * https://tools.ietf.org/html/rfc8259 * #ifdef SIMDJSON_ALLOWANYTHINGINROOT */ case '"': { if (!parse_string(buf, len, pj, depth, idx)) { goto fail; } break; } case 't': { /* we need to make a copy to make sure that the string is space * terminated. * this only applies to the JSON document made solely of the true value. * this will almost never be called in practice */ char *copy = static_cast(malloc(len + SIMDJSON_PADDING)); if (copy == nullptr) { goto fail; } memcpy(copy, buf, len); copy[len] = ' '; if (!is_valid_true_atom(reinterpret_cast(copy) + idx)) { free(copy); goto fail; } free(copy); pj.write_tape(0, c); break; } case 'f': { /* we need to make a copy to make sure that the string is space * terminated. * this only applies to the JSON document made solely of the false * value. * this will almost never be called in practice */ char *copy = static_cast(malloc(len + SIMDJSON_PADDING)); if (copy == nullptr) { goto fail; } memcpy(copy, buf, len); copy[len] = ' '; if (!is_valid_false_atom(reinterpret_cast(copy) + idx)) { free(copy); goto fail; } free(copy); pj.write_tape(0, c); break; } case 'n': { /* we need to make a copy to make sure that the string is space * terminated. * this only applies to the JSON document made solely of the null value. * this will almost never be called in practice */ char *copy = static_cast(malloc(len + SIMDJSON_PADDING)); if (copy == nullptr) { goto fail; } memcpy(copy, buf, len); copy[len] = ' '; if (!is_valid_null_atom(reinterpret_cast(copy) + idx)) { free(copy); goto fail; } free(copy); pj.write_tape(0, c); break; } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { /* we need to make a copy to make sure that the string is space * terminated. * this is done only for JSON documents made of a sole number * this will almost never be called in practice. We terminate with a * space * because we do not want to allow NULLs in the middle of a number * (whereas a * space in the middle of a number would be identified in stage 1). */ char *copy = static_cast(malloc(len + SIMDJSON_PADDING)); if (copy == nullptr) { goto fail; } memcpy(copy, buf, len); copy[len] = ' '; if (!parse_number(reinterpret_cast(copy), pj, idx, false)) { free(copy); goto fail; } free(copy); break; } case '-': { /* we need to make a copy to make sure that the string is NULL * terminated. * this is done only for JSON documents made of a sole number * this will almost never be called in practice */ char *copy = static_cast(malloc(len + SIMDJSON_PADDING)); if (copy == nullptr) { goto fail; } memcpy(copy, buf, len); copy[len] = ' '; if (!parse_number(reinterpret_cast(copy), pj, idx, true)) { free(copy); goto fail; } free(copy); break; } default: goto fail; } start_continue: /* the string might not be NULL terminated. */ if (i + 1 == pj.n_structural_indexes) { goto succeed; } else { goto fail; } /*//////////////////////////// OBJECT STATES ///////////////////////////*/ object_begin: UPDATE_CHAR(); switch (c) { case '"': { if (!parse_string(buf, len, pj, depth, idx)) { goto fail; } goto object_key_state; } case '}': goto scope_end; /* could also go to object_continue */ default: goto fail; } object_key_state: UPDATE_CHAR(); if (c != ':') { goto fail; } UPDATE_CHAR(); switch (c) { case '"': { if (!parse_string(buf, len, pj, depth, idx)) { goto fail; } break; } case 't': if (!is_valid_true_atom(buf + idx)) { goto fail; } pj.write_tape(0, c); break; case 'f': if (!is_valid_false_atom(buf + idx)) { goto fail; } pj.write_tape(0, c); break; case 'n': if (!is_valid_null_atom(buf + idx)) { goto fail; } pj.write_tape(0, c); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { if (!parse_number(buf, pj, idx, false)) { goto fail; } break; } case '-': { if (!parse_number(buf, pj, idx, true)) { goto fail; } break; } case '{': { pj.containing_scope_offset[depth] = pj.get_current_loc(); pj.write_tape(0, c); /* here the compilers knows what c is so this gets optimized */ /* we have not yet encountered } so we need to come back for it */ SET_GOTO_OBJECT_CONTINUE() /* we found an object inside an object, so we need to increment the * depth */ depth++; if (depth >= pj.depth_capacity) { goto fail; } goto object_begin; } case '[': { pj.containing_scope_offset[depth] = pj.get_current_loc(); pj.write_tape(0, c); /* here the compilers knows what c is so this gets optimized */ /* we have not yet encountered } so we need to come back for it */ SET_GOTO_OBJECT_CONTINUE() /* we found an array inside an object, so we need to increment the depth */ depth++; if (depth >= pj.depth_capacity) { goto fail; } goto array_begin; } default: goto fail; } object_continue: UPDATE_CHAR(); switch (c) { case ',': UPDATE_CHAR(); if (c != '"') { goto fail; } else { if (!parse_string(buf, len, pj, depth, idx)) { goto fail; } goto object_key_state; } case '}': goto scope_end; default: goto fail; } /*//////////////////////////// COMMON STATE ///////////////////////////*/ scope_end: /* write our tape location to the header scope */ depth--; pj.write_tape(pj.containing_scope_offset[depth], c); pj.annotate_previous_loc(pj.containing_scope_offset[depth], pj.get_current_loc()); /* goto saved_state */ GOTO_CONTINUE() /*//////////////////////////// ARRAY STATES ///////////////////////////*/ array_begin: UPDATE_CHAR(); if (c == ']') { goto scope_end; /* could also go to array_continue */ } main_array_switch: /* we call update char on all paths in, so we can peek at c on the * on paths that can accept a close square brace (post-, and at start) */ switch (c) { case '"': { if (!parse_string(buf, len, pj, depth, idx)) { goto fail; } break; } case 't': if (!is_valid_true_atom(buf + idx)) { goto fail; } pj.write_tape(0, c); break; case 'f': if (!is_valid_false_atom(buf + idx)) { goto fail; } pj.write_tape(0, c); break; case 'n': if (!is_valid_null_atom(buf + idx)) { goto fail; } pj.write_tape(0, c); break; /* goto array_continue; */ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { if (!parse_number(buf, pj, idx, false)) { goto fail; } break; /* goto array_continue; */ } case '-': { if (!parse_number(buf, pj, idx, true)) { goto fail; } break; /* goto array_continue; */ } case '{': { /* we have not yet encountered ] so we need to come back for it */ pj.containing_scope_offset[depth] = pj.get_current_loc(); pj.write_tape(0, c); /* here the compilers knows what c is so this gets optimized */ SET_GOTO_ARRAY_CONTINUE() /* we found an object inside an array, so we need to increment the depth */ depth++; if (depth >= pj.depth_capacity) { goto fail; } goto object_begin; } case '[': { /* we have not yet encountered ] so we need to come back for it */ pj.containing_scope_offset[depth] = pj.get_current_loc(); pj.write_tape(0, c); /* here the compilers knows what c is so this gets optimized */ SET_GOTO_ARRAY_CONTINUE() /* we found an array inside an array, so we need to increment the depth */ depth++; if (depth >= pj.depth_capacity) { goto fail; } goto array_begin; } default: goto fail; } array_continue: UPDATE_CHAR(); switch (c) { case ',': UPDATE_CHAR(); goto main_array_switch; case ']': goto scope_end; default: goto fail; } /*//////////////////////////// FINAL STATES ///////////////////////////*/ succeed: depth--; if (depth != 0) { fprintf(stderr, "internal bug\n"); abort(); } if (pj.containing_scope_offset[depth] != 0) { fprintf(stderr, "internal bug\n"); abort(); } pj.annotate_previous_loc(pj.containing_scope_offset[depth], pj.get_current_loc()); pj.write_tape(pj.containing_scope_offset[depth], 'r'); /* r is root */ pj.valid = true; pj.error_code = simdjson::SUCCESS; return pj.error_code; fail: /* we do not need the next line because this is done by pj.init(), * pessimistically. * pj.is_valid = false; * At this point in the code, we have all the time in the world. * Note that we know exactly where we are in the document so we could, * without any overhead on the processing code, report a specific * location. * We could even trigger special code paths to assess what happened * carefully, * all without any added cost. */ if (depth >= pj.depth_capacity) { pj.error_code = simdjson::DEPTH_ERROR; return pj.error_code; } switch (c) { case '"': pj.error_code = simdjson::STRING_ERROR; return pj.error_code; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '-': pj.error_code = simdjson::NUMBER_ERROR; return pj.error_code; case 't': pj.error_code = simdjson::T_ATOM_ERROR; return pj.error_code; case 'n': pj.error_code = simdjson::N_ATOM_ERROR; return pj.error_code; case 'f': pj.error_code = simdjson::F_ATOM_ERROR; return pj.error_code; default: break; } pj.error_code = simdjson::TAPE_ERROR; return pj.error_code; } } // namespace simdjson::arm64 namespace simdjson { template <> WARN_UNUSED int unified_machine(const uint8_t *buf, size_t len, ParsedJson &pj) { return arm64::unified_machine(buf, len, pj); } } // namespace simdjson #endif // IS_ARM64 #endif // SIMDJSON_ARM64_STAGE2_BUILD_TAPE_H /* end file src/arm64/stage2_build_tape.h */ /* begin file src/haswell/stage2_build_tape.h */ #ifndef SIMDJSON_HASWELL_STAGE2_BUILD_TAPE_H #define SIMDJSON_HASWELL_STAGE2_BUILD_TAPE_H #ifdef IS_X86_64 TARGET_HASWELL namespace simdjson::haswell { // This file contains the common code every implementation uses for stage2 // It is intended to be included multiple times and compiled multiple times // We assume the file in which it is include already includes // "simdjson/stage2_build_tape.h" (this simplifies amalgation) // this macro reads the next structural character, updating idx, i and c. #define UPDATE_CHAR() \ { \ idx = pj.structural_indexes[i++]; \ c = buf[idx]; \ } #ifdef SIMDJSON_USE_COMPUTED_GOTO #define SET_GOTO_ARRAY_CONTINUE() pj.ret_address[depth] = &&array_continue; #define SET_GOTO_OBJECT_CONTINUE() pj.ret_address[depth] = &&object_continue; #define SET_GOTO_START_CONTINUE() pj.ret_address[depth] = &&start_continue; #define GOTO_CONTINUE() goto *pj.ret_address[depth]; #else #define SET_GOTO_ARRAY_CONTINUE() pj.ret_address[depth] = 'a'; #define SET_GOTO_OBJECT_CONTINUE() pj.ret_address[depth] = 'o'; #define SET_GOTO_START_CONTINUE() pj.ret_address[depth] = 's'; #define GOTO_CONTINUE() \ { \ if (pj.ret_address[depth] == 'a') { \ goto array_continue; \ } else if (pj.ret_address[depth] == 'o') { \ goto object_continue; \ } else { \ goto start_continue; \ } \ } #endif /************ * The JSON is parsed to a tape, see the accompanying tape.md file * for documentation. ***********/ WARN_UNUSED int unified_machine(const uint8_t *buf, size_t len, ParsedJson &pj) { uint32_t i = 0; /* index of the structural character (0,1,2,3...) */ uint32_t idx; /* location of the structural character in the input (buf) */ uint8_t c; /* used to track the (structural) character we are looking at, updated */ /* by UPDATE_CHAR macro */ uint32_t depth = 0; /* could have an arbitrary starting depth */ pj.init(); /* sets is_valid to false */ if (pj.byte_capacity < len) { pj.error_code = simdjson::CAPACITY; return pj.error_code; } /*//////////////////////////// START STATE ///////////////////////////// */ SET_GOTO_START_CONTINUE() pj.containing_scope_offset[depth] = pj.get_current_loc(); pj.write_tape(0, 'r'); /* r for root, 0 is going to get overwritten */ /* the root is used, if nothing else, to capture the size of the tape */ depth++; /* everything starts at depth = 1, depth = 0 is just for the root, the root may contain an object, an array or something else. */ if (depth >= pj.depth_capacity) { goto fail; } UPDATE_CHAR(); switch (c) { case '{': pj.containing_scope_offset[depth] = pj.get_current_loc(); SET_GOTO_START_CONTINUE(); depth++; if (depth >= pj.depth_capacity) { goto fail; } pj.write_tape( 0, c); /* strangely, moving this to object_begin slows things down */ goto object_begin; case '[': pj.containing_scope_offset[depth] = pj.get_current_loc(); SET_GOTO_START_CONTINUE(); depth++; if (depth >= pj.depth_capacity) { goto fail; } pj.write_tape(0, c); goto array_begin; /* #define SIMDJSON_ALLOWANYTHINGINROOT * A JSON text is a serialized value. Note that certain previous * specifications of JSON constrained a JSON text to be an object or an * array. Implementations that generate only objects or arrays where a * JSON text is called for will be interoperable in the sense that all * implementations will accept these as conforming JSON texts. * https://tools.ietf.org/html/rfc8259 * #ifdef SIMDJSON_ALLOWANYTHINGINROOT */ case '"': { if (!parse_string(buf, len, pj, depth, idx)) { goto fail; } break; } case 't': { /* we need to make a copy to make sure that the string is space * terminated. * this only applies to the JSON document made solely of the true value. * this will almost never be called in practice */ char *copy = static_cast(malloc(len + SIMDJSON_PADDING)); if (copy == nullptr) { goto fail; } memcpy(copy, buf, len); copy[len] = ' '; if (!is_valid_true_atom(reinterpret_cast(copy) + idx)) { free(copy); goto fail; } free(copy); pj.write_tape(0, c); break; } case 'f': { /* we need to make a copy to make sure that the string is space * terminated. * this only applies to the JSON document made solely of the false * value. * this will almost never be called in practice */ char *copy = static_cast(malloc(len + SIMDJSON_PADDING)); if (copy == nullptr) { goto fail; } memcpy(copy, buf, len); copy[len] = ' '; if (!is_valid_false_atom(reinterpret_cast(copy) + idx)) { free(copy); goto fail; } free(copy); pj.write_tape(0, c); break; } case 'n': { /* we need to make a copy to make sure that the string is space * terminated. * this only applies to the JSON document made solely of the null value. * this will almost never be called in practice */ char *copy = static_cast(malloc(len + SIMDJSON_PADDING)); if (copy == nullptr) { goto fail; } memcpy(copy, buf, len); copy[len] = ' '; if (!is_valid_null_atom(reinterpret_cast(copy) + idx)) { free(copy); goto fail; } free(copy); pj.write_tape(0, c); break; } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { /* we need to make a copy to make sure that the string is space * terminated. * this is done only for JSON documents made of a sole number * this will almost never be called in practice. We terminate with a * space * because we do not want to allow NULLs in the middle of a number * (whereas a * space in the middle of a number would be identified in stage 1). */ char *copy = static_cast(malloc(len + SIMDJSON_PADDING)); if (copy == nullptr) { goto fail; } memcpy(copy, buf, len); copy[len] = ' '; if (!parse_number(reinterpret_cast(copy), pj, idx, false)) { free(copy); goto fail; } free(copy); break; } case '-': { /* we need to make a copy to make sure that the string is NULL * terminated. * this is done only for JSON documents made of a sole number * this will almost never be called in practice */ char *copy = static_cast(malloc(len + SIMDJSON_PADDING)); if (copy == nullptr) { goto fail; } memcpy(copy, buf, len); copy[len] = ' '; if (!parse_number(reinterpret_cast(copy), pj, idx, true)) { free(copy); goto fail; } free(copy); break; } default: goto fail; } start_continue: /* the string might not be NULL terminated. */ if (i + 1 == pj.n_structural_indexes) { goto succeed; } else { goto fail; } /*//////////////////////////// OBJECT STATES ///////////////////////////*/ object_begin: UPDATE_CHAR(); switch (c) { case '"': { if (!parse_string(buf, len, pj, depth, idx)) { goto fail; } goto object_key_state; } case '}': goto scope_end; /* could also go to object_continue */ default: goto fail; } object_key_state: UPDATE_CHAR(); if (c != ':') { goto fail; } UPDATE_CHAR(); switch (c) { case '"': { if (!parse_string(buf, len, pj, depth, idx)) { goto fail; } break; } case 't': if (!is_valid_true_atom(buf + idx)) { goto fail; } pj.write_tape(0, c); break; case 'f': if (!is_valid_false_atom(buf + idx)) { goto fail; } pj.write_tape(0, c); break; case 'n': if (!is_valid_null_atom(buf + idx)) { goto fail; } pj.write_tape(0, c); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { if (!parse_number(buf, pj, idx, false)) { goto fail; } break; } case '-': { if (!parse_number(buf, pj, idx, true)) { goto fail; } break; } case '{': { pj.containing_scope_offset[depth] = pj.get_current_loc(); pj.write_tape(0, c); /* here the compilers knows what c is so this gets optimized */ /* we have not yet encountered } so we need to come back for it */ SET_GOTO_OBJECT_CONTINUE() /* we found an object inside an object, so we need to increment the * depth */ depth++; if (depth >= pj.depth_capacity) { goto fail; } goto object_begin; } case '[': { pj.containing_scope_offset[depth] = pj.get_current_loc(); pj.write_tape(0, c); /* here the compilers knows what c is so this gets optimized */ /* we have not yet encountered } so we need to come back for it */ SET_GOTO_OBJECT_CONTINUE() /* we found an array inside an object, so we need to increment the depth */ depth++; if (depth >= pj.depth_capacity) { goto fail; } goto array_begin; } default: goto fail; } object_continue: UPDATE_CHAR(); switch (c) { case ',': UPDATE_CHAR(); if (c != '"') { goto fail; } else { if (!parse_string(buf, len, pj, depth, idx)) { goto fail; } goto object_key_state; } case '}': goto scope_end; default: goto fail; } /*//////////////////////////// COMMON STATE ///////////////////////////*/ scope_end: /* write our tape location to the header scope */ depth--; pj.write_tape(pj.containing_scope_offset[depth], c); pj.annotate_previous_loc(pj.containing_scope_offset[depth], pj.get_current_loc()); /* goto saved_state */ GOTO_CONTINUE() /*//////////////////////////// ARRAY STATES ///////////////////////////*/ array_begin: UPDATE_CHAR(); if (c == ']') { goto scope_end; /* could also go to array_continue */ } main_array_switch: /* we call update char on all paths in, so we can peek at c on the * on paths that can accept a close square brace (post-, and at start) */ switch (c) { case '"': { if (!parse_string(buf, len, pj, depth, idx)) { goto fail; } break; } case 't': if (!is_valid_true_atom(buf + idx)) { goto fail; } pj.write_tape(0, c); break; case 'f': if (!is_valid_false_atom(buf + idx)) { goto fail; } pj.write_tape(0, c); break; case 'n': if (!is_valid_null_atom(buf + idx)) { goto fail; } pj.write_tape(0, c); break; /* goto array_continue; */ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { if (!parse_number(buf, pj, idx, false)) { goto fail; } break; /* goto array_continue; */ } case '-': { if (!parse_number(buf, pj, idx, true)) { goto fail; } break; /* goto array_continue; */ } case '{': { /* we have not yet encountered ] so we need to come back for it */ pj.containing_scope_offset[depth] = pj.get_current_loc(); pj.write_tape(0, c); /* here the compilers knows what c is so this gets optimized */ SET_GOTO_ARRAY_CONTINUE() /* we found an object inside an array, so we need to increment the depth */ depth++; if (depth >= pj.depth_capacity) { goto fail; } goto object_begin; } case '[': { /* we have not yet encountered ] so we need to come back for it */ pj.containing_scope_offset[depth] = pj.get_current_loc(); pj.write_tape(0, c); /* here the compilers knows what c is so this gets optimized */ SET_GOTO_ARRAY_CONTINUE() /* we found an array inside an array, so we need to increment the depth */ depth++; if (depth >= pj.depth_capacity) { goto fail; } goto array_begin; } default: goto fail; } array_continue: UPDATE_CHAR(); switch (c) { case ',': UPDATE_CHAR(); goto main_array_switch; case ']': goto scope_end; default: goto fail; } /*//////////////////////////// FINAL STATES ///////////////////////////*/ succeed: depth--; if (depth != 0) { fprintf(stderr, "internal bug\n"); abort(); } if (pj.containing_scope_offset[depth] != 0) { fprintf(stderr, "internal bug\n"); abort(); } pj.annotate_previous_loc(pj.containing_scope_offset[depth], pj.get_current_loc()); pj.write_tape(pj.containing_scope_offset[depth], 'r'); /* r is root */ pj.valid = true; pj.error_code = simdjson::SUCCESS; return pj.error_code; fail: /* we do not need the next line because this is done by pj.init(), * pessimistically. * pj.is_valid = false; * At this point in the code, we have all the time in the world. * Note that we know exactly where we are in the document so we could, * without any overhead on the processing code, report a specific * location. * We could even trigger special code paths to assess what happened * carefully, * all without any added cost. */ if (depth >= pj.depth_capacity) { pj.error_code = simdjson::DEPTH_ERROR; return pj.error_code; } switch (c) { case '"': pj.error_code = simdjson::STRING_ERROR; return pj.error_code; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '-': pj.error_code = simdjson::NUMBER_ERROR; return pj.error_code; case 't': pj.error_code = simdjson::T_ATOM_ERROR; return pj.error_code; case 'n': pj.error_code = simdjson::N_ATOM_ERROR; return pj.error_code; case 'f': pj.error_code = simdjson::F_ATOM_ERROR; return pj.error_code; default: break; } pj.error_code = simdjson::TAPE_ERROR; return pj.error_code; } } // namespace simdjson::haswell UNTARGET_REGION TARGET_HASWELL namespace simdjson { template <> WARN_UNUSED int unified_machine(const uint8_t *buf, size_t len, ParsedJson &pj) { return haswell::unified_machine(buf, len, pj); } } // namespace simdjson UNTARGET_REGION #endif // IS_X86_64 #endif // SIMDJSON_HASWELL_STAGE2_BUILD_TAPE_H /* end file src/haswell/stage2_build_tape.h */ /* begin file src/westmere/stage2_build_tape.h */ #ifndef SIMDJSON_WESTMERE_STAGE2_BUILD_TAPE_H #define SIMDJSON_WESTMERE_STAGE2_BUILD_TAPE_H #ifdef IS_X86_64 TARGET_WESTMERE namespace simdjson::westmere { // This file contains the common code every implementation uses for stage2 // It is intended to be included multiple times and compiled multiple times // We assume the file in which it is include already includes // "simdjson/stage2_build_tape.h" (this simplifies amalgation) // this macro reads the next structural character, updating idx, i and c. #define UPDATE_CHAR() \ { \ idx = pj.structural_indexes[i++]; \ c = buf[idx]; \ } #ifdef SIMDJSON_USE_COMPUTED_GOTO #define SET_GOTO_ARRAY_CONTINUE() pj.ret_address[depth] = &&array_continue; #define SET_GOTO_OBJECT_CONTINUE() pj.ret_address[depth] = &&object_continue; #define SET_GOTO_START_CONTINUE() pj.ret_address[depth] = &&start_continue; #define GOTO_CONTINUE() goto *pj.ret_address[depth]; #else #define SET_GOTO_ARRAY_CONTINUE() pj.ret_address[depth] = 'a'; #define SET_GOTO_OBJECT_CONTINUE() pj.ret_address[depth] = 'o'; #define SET_GOTO_START_CONTINUE() pj.ret_address[depth] = 's'; #define GOTO_CONTINUE() \ { \ if (pj.ret_address[depth] == 'a') { \ goto array_continue; \ } else if (pj.ret_address[depth] == 'o') { \ goto object_continue; \ } else { \ goto start_continue; \ } \ } #endif /************ * The JSON is parsed to a tape, see the accompanying tape.md file * for documentation. ***********/ WARN_UNUSED int unified_machine(const uint8_t *buf, size_t len, ParsedJson &pj) { uint32_t i = 0; /* index of the structural character (0,1,2,3...) */ uint32_t idx; /* location of the structural character in the input (buf) */ uint8_t c; /* used to track the (structural) character we are looking at, updated */ /* by UPDATE_CHAR macro */ uint32_t depth = 0; /* could have an arbitrary starting depth */ pj.init(); /* sets is_valid to false */ if (pj.byte_capacity < len) { pj.error_code = simdjson::CAPACITY; return pj.error_code; } /*//////////////////////////// START STATE ///////////////////////////// */ SET_GOTO_START_CONTINUE() pj.containing_scope_offset[depth] = pj.get_current_loc(); pj.write_tape(0, 'r'); /* r for root, 0 is going to get overwritten */ /* the root is used, if nothing else, to capture the size of the tape */ depth++; /* everything starts at depth = 1, depth = 0 is just for the root, the root may contain an object, an array or something else. */ if (depth >= pj.depth_capacity) { goto fail; } UPDATE_CHAR(); switch (c) { case '{': pj.containing_scope_offset[depth] = pj.get_current_loc(); SET_GOTO_START_CONTINUE(); depth++; if (depth >= pj.depth_capacity) { goto fail; } pj.write_tape( 0, c); /* strangely, moving this to object_begin slows things down */ goto object_begin; case '[': pj.containing_scope_offset[depth] = pj.get_current_loc(); SET_GOTO_START_CONTINUE(); depth++; if (depth >= pj.depth_capacity) { goto fail; } pj.write_tape(0, c); goto array_begin; /* #define SIMDJSON_ALLOWANYTHINGINROOT * A JSON text is a serialized value. Note that certain previous * specifications of JSON constrained a JSON text to be an object or an * array. Implementations that generate only objects or arrays where a * JSON text is called for will be interoperable in the sense that all * implementations will accept these as conforming JSON texts. * https://tools.ietf.org/html/rfc8259 * #ifdef SIMDJSON_ALLOWANYTHINGINROOT */ case '"': { if (!parse_string(buf, len, pj, depth, idx)) { goto fail; } break; } case 't': { /* we need to make a copy to make sure that the string is space * terminated. * this only applies to the JSON document made solely of the true value. * this will almost never be called in practice */ char *copy = static_cast(malloc(len + SIMDJSON_PADDING)); if (copy == nullptr) { goto fail; } memcpy(copy, buf, len); copy[len] = ' '; if (!is_valid_true_atom(reinterpret_cast(copy) + idx)) { free(copy); goto fail; } free(copy); pj.write_tape(0, c); break; } case 'f': { /* we need to make a copy to make sure that the string is space * terminated. * this only applies to the JSON document made solely of the false * value. * this will almost never be called in practice */ char *copy = static_cast(malloc(len + SIMDJSON_PADDING)); if (copy == nullptr) { goto fail; } memcpy(copy, buf, len); copy[len] = ' '; if (!is_valid_false_atom(reinterpret_cast(copy) + idx)) { free(copy); goto fail; } free(copy); pj.write_tape(0, c); break; } case 'n': { /* we need to make a copy to make sure that the string is space * terminated. * this only applies to the JSON document made solely of the null value. * this will almost never be called in practice */ char *copy = static_cast(malloc(len + SIMDJSON_PADDING)); if (copy == nullptr) { goto fail; } memcpy(copy, buf, len); copy[len] = ' '; if (!is_valid_null_atom(reinterpret_cast(copy) + idx)) { free(copy); goto fail; } free(copy); pj.write_tape(0, c); break; } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { /* we need to make a copy to make sure that the string is space * terminated. * this is done only for JSON documents made of a sole number * this will almost never be called in practice. We terminate with a * space * because we do not want to allow NULLs in the middle of a number * (whereas a * space in the middle of a number would be identified in stage 1). */ char *copy = static_cast(malloc(len + SIMDJSON_PADDING)); if (copy == nullptr) { goto fail; } memcpy(copy, buf, len); copy[len] = ' '; if (!parse_number(reinterpret_cast(copy), pj, idx, false)) { free(copy); goto fail; } free(copy); break; } case '-': { /* we need to make a copy to make sure that the string is NULL * terminated. * this is done only for JSON documents made of a sole number * this will almost never be called in practice */ char *copy = static_cast(malloc(len + SIMDJSON_PADDING)); if (copy == nullptr) { goto fail; } memcpy(copy, buf, len); copy[len] = ' '; if (!parse_number(reinterpret_cast(copy), pj, idx, true)) { free(copy); goto fail; } free(copy); break; } default: goto fail; } start_continue: /* the string might not be NULL terminated. */ if (i + 1 == pj.n_structural_indexes) { goto succeed; } else { goto fail; } /*//////////////////////////// OBJECT STATES ///////////////////////////*/ object_begin: UPDATE_CHAR(); switch (c) { case '"': { if (!parse_string(buf, len, pj, depth, idx)) { goto fail; } goto object_key_state; } case '}': goto scope_end; /* could also go to object_continue */ default: goto fail; } object_key_state: UPDATE_CHAR(); if (c != ':') { goto fail; } UPDATE_CHAR(); switch (c) { case '"': { if (!parse_string(buf, len, pj, depth, idx)) { goto fail; } break; } case 't': if (!is_valid_true_atom(buf + idx)) { goto fail; } pj.write_tape(0, c); break; case 'f': if (!is_valid_false_atom(buf + idx)) { goto fail; } pj.write_tape(0, c); break; case 'n': if (!is_valid_null_atom(buf + idx)) { goto fail; } pj.write_tape(0, c); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { if (!parse_number(buf, pj, idx, false)) { goto fail; } break; } case '-': { if (!parse_number(buf, pj, idx, true)) { goto fail; } break; } case '{': { pj.containing_scope_offset[depth] = pj.get_current_loc(); pj.write_tape(0, c); /* here the compilers knows what c is so this gets optimized */ /* we have not yet encountered } so we need to come back for it */ SET_GOTO_OBJECT_CONTINUE() /* we found an object inside an object, so we need to increment the * depth */ depth++; if (depth >= pj.depth_capacity) { goto fail; } goto object_begin; } case '[': { pj.containing_scope_offset[depth] = pj.get_current_loc(); pj.write_tape(0, c); /* here the compilers knows what c is so this gets optimized */ /* we have not yet encountered } so we need to come back for it */ SET_GOTO_OBJECT_CONTINUE() /* we found an array inside an object, so we need to increment the depth */ depth++; if (depth >= pj.depth_capacity) { goto fail; } goto array_begin; } default: goto fail; } object_continue: UPDATE_CHAR(); switch (c) { case ',': UPDATE_CHAR(); if (c != '"') { goto fail; } else { if (!parse_string(buf, len, pj, depth, idx)) { goto fail; } goto object_key_state; } case '}': goto scope_end; default: goto fail; } /*//////////////////////////// COMMON STATE ///////////////////////////*/ scope_end: /* write our tape location to the header scope */ depth--; pj.write_tape(pj.containing_scope_offset[depth], c); pj.annotate_previous_loc(pj.containing_scope_offset[depth], pj.get_current_loc()); /* goto saved_state */ GOTO_CONTINUE() /*//////////////////////////// ARRAY STATES ///////////////////////////*/ array_begin: UPDATE_CHAR(); if (c == ']') { goto scope_end; /* could also go to array_continue */ } main_array_switch: /* we call update char on all paths in, so we can peek at c on the * on paths that can accept a close square brace (post-, and at start) */ switch (c) { case '"': { if (!parse_string(buf, len, pj, depth, idx)) { goto fail; } break; } case 't': if (!is_valid_true_atom(buf + idx)) { goto fail; } pj.write_tape(0, c); break; case 'f': if (!is_valid_false_atom(buf + idx)) { goto fail; } pj.write_tape(0, c); break; case 'n': if (!is_valid_null_atom(buf + idx)) { goto fail; } pj.write_tape(0, c); break; /* goto array_continue; */ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { if (!parse_number(buf, pj, idx, false)) { goto fail; } break; /* goto array_continue; */ } case '-': { if (!parse_number(buf, pj, idx, true)) { goto fail; } break; /* goto array_continue; */ } case '{': { /* we have not yet encountered ] so we need to come back for it */ pj.containing_scope_offset[depth] = pj.get_current_loc(); pj.write_tape(0, c); /* here the compilers knows what c is so this gets optimized */ SET_GOTO_ARRAY_CONTINUE() /* we found an object inside an array, so we need to increment the depth */ depth++; if (depth >= pj.depth_capacity) { goto fail; } goto object_begin; } case '[': { /* we have not yet encountered ] so we need to come back for it */ pj.containing_scope_offset[depth] = pj.get_current_loc(); pj.write_tape(0, c); /* here the compilers knows what c is so this gets optimized */ SET_GOTO_ARRAY_CONTINUE() /* we found an array inside an array, so we need to increment the depth */ depth++; if (depth >= pj.depth_capacity) { goto fail; } goto array_begin; } default: goto fail; } array_continue: UPDATE_CHAR(); switch (c) { case ',': UPDATE_CHAR(); goto main_array_switch; case ']': goto scope_end; default: goto fail; } /*//////////////////////////// FINAL STATES ///////////////////////////*/ succeed: depth--; if (depth != 0) { fprintf(stderr, "internal bug\n"); abort(); } if (pj.containing_scope_offset[depth] != 0) { fprintf(stderr, "internal bug\n"); abort(); } pj.annotate_previous_loc(pj.containing_scope_offset[depth], pj.get_current_loc()); pj.write_tape(pj.containing_scope_offset[depth], 'r'); /* r is root */ pj.valid = true; pj.error_code = simdjson::SUCCESS; return pj.error_code; fail: /* we do not need the next line because this is done by pj.init(), * pessimistically. * pj.is_valid = false; * At this point in the code, we have all the time in the world. * Note that we know exactly where we are in the document so we could, * without any overhead on the processing code, report a specific * location. * We could even trigger special code paths to assess what happened * carefully, * all without any added cost. */ if (depth >= pj.depth_capacity) { pj.error_code = simdjson::DEPTH_ERROR; return pj.error_code; } switch (c) { case '"': pj.error_code = simdjson::STRING_ERROR; return pj.error_code; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '-': pj.error_code = simdjson::NUMBER_ERROR; return pj.error_code; case 't': pj.error_code = simdjson::T_ATOM_ERROR; return pj.error_code; case 'n': pj.error_code = simdjson::N_ATOM_ERROR; return pj.error_code; case 'f': pj.error_code = simdjson::F_ATOM_ERROR; return pj.error_code; default: break; } pj.error_code = simdjson::TAPE_ERROR; return pj.error_code; } } // namespace simdjson::westmere UNTARGET_REGION TARGET_WESTMERE namespace simdjson { template <> WARN_UNUSED int unified_machine(const uint8_t *buf, size_t len, ParsedJson &pj) { return westmere::unified_machine(buf, len, pj); } } // namespace simdjson UNTARGET_REGION #endif // IS_X86_64 #endif // SIMDJSON_WESTMERE_STAGE2_BUILD_TAPE_H /* end file src/westmere/stage2_build_tape.h */ /* begin file src/stage2_build_tape.cpp */ /* end file src/stage2_build_tape.cpp */ /* begin file src/parsedjson.cpp */ namespace simdjson { ParsedJson::ParsedJson() : structural_indexes(nullptr), tape(nullptr), containing_scope_offset(nullptr), ret_address(nullptr), string_buf(nullptr), current_string_buf_loc(nullptr) {} ParsedJson::~ParsedJson() { deallocate(); } ParsedJson::ParsedJson(ParsedJson &&p) : byte_capacity(p.byte_capacity), depth_capacity(p.depth_capacity), tape_capacity(p.tape_capacity), string_capacity(p.string_capacity), current_loc(p.current_loc), n_structural_indexes(p.n_structural_indexes), structural_indexes(p.structural_indexes), tape(p.tape), containing_scope_offset(p.containing_scope_offset), ret_address(p.ret_address), string_buf(p.string_buf), current_string_buf_loc(p.current_string_buf_loc), valid(p.valid) { p.structural_indexes = nullptr; p.tape = nullptr; p.containing_scope_offset = nullptr; p.ret_address = nullptr; p.string_buf = nullptr; p.current_string_buf_loc = nullptr; } WARN_UNUSED bool ParsedJson::allocate_capacity(size_t len, size_t max_depth) { if (max_depth <= 0) { max_depth = 1; // don't let the user allocate nothing } if (len <= 0) { len = 64; // allocating 0 bytes is wasteful. } if (len > SIMDJSON_MAXSIZE_BYTES) { return false; } if ((len <= byte_capacity) && (max_depth <= depth_capacity)) { return true; } deallocate(); valid = false; byte_capacity = 0; // will only set it to len after allocations are a success n_structural_indexes = 0; uint32_t max_structures = ROUNDUP_N(len, 64) + 2 + 7; structural_indexes = new (std::nothrow) uint32_t[max_structures]; // a pathological input like "[[[[..." would generate len tape elements, so // need a capacity of len + 1 size_t local_tape_capacity = ROUNDUP_N(len + 1, 64); // a document with only zero-length strings... could have len/3 string // and we would need len/3 * 5 bytes on the string buffer size_t local_string_capacity = ROUNDUP_N(5 * len / 3 + 32, 64); string_buf = new (std::nothrow) uint8_t[local_string_capacity]; tape = new (std::nothrow) uint64_t[local_tape_capacity]; containing_scope_offset = new (std::nothrow) uint32_t[max_depth]; #ifdef SIMDJSON_USE_COMPUTED_GOTO ret_address = new (std::nothrow) void *[max_depth]; #else ret_address = new (std::nothrow) char[max_depth]; #endif if ((string_buf == nullptr) || (tape == nullptr) || (containing_scope_offset == nullptr) || (ret_address == nullptr) || (structural_indexes == nullptr)) { std::cerr << "Could not allocate memory" << std::endl; delete[] ret_address; delete[] containing_scope_offset; delete[] tape; delete[] string_buf; delete[] structural_indexes; return false; } /* // We do not need to initialize this content for parsing, though we could // need to initialize it for safety. memset(string_buf, 0 , local_string_capacity); memset(structural_indexes, 0, max_structures * sizeof(uint32_t)); memset(tape, 0, local_tape_capacity * sizeof(uint64_t)); */ byte_capacity = len; depth_capacity = max_depth; tape_capacity = local_tape_capacity; string_capacity = local_string_capacity; return true; } bool ParsedJson::is_valid() const { return valid; } int ParsedJson::get_error_code() const { return error_code; } std::string ParsedJson::get_error_message() const { return error_message(error_code); } void ParsedJson::deallocate() { byte_capacity = 0; depth_capacity = 0; tape_capacity = 0; string_capacity = 0; delete[] ret_address; delete[] containing_scope_offset; delete[] tape; delete[] string_buf; delete[] structural_indexes; valid = false; } void ParsedJson::init() { current_string_buf_loc = string_buf; current_loc = 0; valid = false; } WARN_UNUSED bool ParsedJson::print_json(std::ostream &os) const { if (!valid) { return false; } uint32_t string_length; size_t tape_idx = 0; uint64_t tape_val = tape[tape_idx]; uint8_t type = (tape_val >> 56); size_t how_many = 0; if (type == 'r') { how_many = tape_val & JSON_VALUE_MASK; } else { fprintf(stderr, "Error: no starting root node?"); return false; } if (how_many > tape_capacity) { fprintf( stderr, "We may be exceeding the tape capacity. Is this a valid document?\n"); return false; } tape_idx++; bool *in_object = new bool[depth_capacity]; auto *in_object_idx = new size_t[depth_capacity]; int depth = 1; // only root at level 0 in_object_idx[depth] = 0; in_object[depth] = false; for (; tape_idx < how_many; tape_idx++) { tape_val = tape[tape_idx]; uint64_t payload = tape_val & JSON_VALUE_MASK; type = (tape_val >> 56); if (!in_object[depth]) { if ((in_object_idx[depth] > 0) && (type != ']')) { os << ","; } in_object_idx[depth]++; } else { // if (in_object) { if ((in_object_idx[depth] > 0) && ((in_object_idx[depth] & 1) == 0) && (type != '}')) { os << ","; } if (((in_object_idx[depth] & 1) == 1)) { os << ":"; } in_object_idx[depth]++; } switch (type) { case '"': // we have a string os << '"'; memcpy(&string_length, string_buf + payload, sizeof(uint32_t)); print_with_escapes( (const unsigned char *)(string_buf + payload + sizeof(uint32_t)), string_length); os << '"'; break; case 'l': // we have a long int if (tape_idx + 1 >= how_many) { delete[] in_object; delete[] in_object_idx; return false; } os << static_cast(tape[++tape_idx]); break; case 'u': if (tape_idx + 1 >= how_many) { delete[] in_object; delete[] in_object_idx; return false; } os << tape[++tape_idx]; break; case 'd': // we have a double if (tape_idx + 1 >= how_many) { delete[] in_object; delete[] in_object_idx; return false; } double answer; memcpy(&answer, &tape[++tape_idx], sizeof(answer)); os << answer; break; case 'n': // we have a null os << "null"; break; case 't': // we have a true os << "true"; break; case 'f': // we have a false os << "false"; break; case '{': // we have an object os << '{'; depth++; in_object[depth] = true; in_object_idx[depth] = 0; break; case '}': // we end an object depth--; os << '}'; break; case '[': // we start an array os << '['; depth++; in_object[depth] = false; in_object_idx[depth] = 0; break; case ']': // we end an array depth--; os << ']'; break; case 'r': // we start and end with the root node fprintf(stderr, "should we be hitting the root node?\n"); delete[] in_object; delete[] in_object_idx; return false; default: fprintf(stderr, "bug %c\n", type); delete[] in_object; delete[] in_object_idx; return false; } } delete[] in_object; delete[] in_object_idx; return true; } WARN_UNUSED bool ParsedJson::dump_raw_tape(std::ostream &os) const { if (!valid) { return false; } uint32_t string_length; size_t tape_idx = 0; uint64_t tape_val = tape[tape_idx]; uint8_t type = (tape_val >> 56); os << tape_idx << " : " << type; tape_idx++; size_t how_many = 0; if (type == 'r') { how_many = tape_val & JSON_VALUE_MASK; } else { fprintf(stderr, "Error: no starting root node?"); return false; } os << "\t// pointing to " << how_many << " (right after last node)\n"; uint64_t payload; for (; tape_idx < how_many; tape_idx++) { os << tape_idx << " : "; tape_val = tape[tape_idx]; payload = tape_val & JSON_VALUE_MASK; type = (tape_val >> 56); switch (type) { case '"': // we have a string os << "string \""; memcpy(&string_length, string_buf + payload, sizeof(uint32_t)); print_with_escapes( (const unsigned char *)(string_buf + payload + sizeof(uint32_t)), string_length); os << '"'; os << '\n'; break; case 'l': // we have a long int if (tape_idx + 1 >= how_many) { return false; } os << "integer " << static_cast(tape[++tape_idx]) << "\n"; break; case 'u': // we have a long uint if (tape_idx + 1 >= how_many) { return false; } os << "unsigned integer " << tape[++tape_idx] << "\n"; break; case 'd': // we have a double os << "float "; if (tape_idx + 1 >= how_many) { return false; } double answer; memcpy(&answer, &tape[++tape_idx], sizeof(answer)); os << answer << '\n'; break; case 'n': // we have a null os << "null\n"; break; case 't': // we have a true os << "true\n"; break; case 'f': // we have a false os << "false\n"; break; case '{': // we have an object os << "{\t// pointing to next tape location " << payload << " (first node after the scope) \n"; break; case '}': // we end an object os << "}\t// pointing to previous tape location " << payload << " (start of the scope) \n"; break; case '[': // we start an array os << "[\t// pointing to next tape location " << payload << " (first node after the scope) \n"; break; case ']': // we end an array os << "]\t// pointing to previous tape location " << payload << " (start of the scope) \n"; break; case 'r': // we start and end with the root node printf("end of root\n"); return false; default: return false; } } tape_val = tape[tape_idx]; payload = tape_val & JSON_VALUE_MASK; type = (tape_val >> 56); os << tape_idx << " : " << type << "\t// pointing to " << payload << " (start root)\n"; return true; } } // namespace simdjson /* end file src/parsedjson.cpp */ /* begin file src/parsedjsoniterator.cpp */ namespace simdjson { template class ParsedJson::BasicIterator; } // namespace simdjson /* end file src/parsedjsoniterator.cpp */