From 7de7ce5fdcdb9a97e6461ad9b5991eb7c44dddde Mon Sep 17 00:00:00 2001 From: John Keiser Date: Tue, 2 Jun 2020 20:21:46 -0700 Subject: [PATCH 1/8] Move document stream state to implementation --- src/generic/stage1/json_structural_indexer.h | 89 ++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/src/generic/stage1/json_structural_indexer.h b/src/generic/stage1/json_structural_indexer.h index 6f80123e7..4f43d24a4 100644 --- a/src/generic/stage1/json_structural_indexer.h +++ b/src/generic/stage1/json_structural_indexer.h @@ -73,6 +73,8 @@ private: really_inline void step(const uint8_t *block, buf_block_reader &reader) noexcept; really_inline void next(simd::simd8x64 in, json_block block, size_t idx); really_inline error_code finish(dom_parser_implementation &parser, size_t idx, size_t len, bool partial); + static really_inline uint32_t find_next_document_index(dom_parser_implementation &parser); + static really_inline size_t trim_partial_utf8(const uint8_t *buf, size_t len); json_scanner scanner{}; utf8_checker checker{}; @@ -195,4 +197,91 @@ really_inline error_code json_structural_indexer::finish(dom_parser_implementati return checker.errors(); } +/** + * This algorithm is used to quickly identify the last structural position that + * makes up a complete document. + * + * It does this by going backwards and finding the last *document boundary* (a + * place where one value follows another without a comma between them). If the + * last document (the characters after the boundary) has an equal number of + * start and end brackets, it is considered complete. + * + * Simply put, we iterate over the structural characters, starting from + * the end. We consider that we found the end of a JSON document when the + * first element of the pair is NOT one of these characters: '{' '[' ';' ',' + * and when the second element is NOT one of these characters: '}' '}' ';' ','. + * + * This simple comparison works most of the time, but it does not cover cases + * where the batch's structural indexes contain a perfect amount of documents. + * In such a case, we do not have access to the structural index which follows + * the last document, therefore, we do not have access to the second element in + * the pair, and means that we cannot identify the last document. To fix this + * issue, we keep a count of the open and closed curly/square braces we found + * while searching for the pair. When we find a pair AND the count of open and + * closed curly/square braces is the same, we know that we just passed a + * complete + * document, therefore the last json buffer location is the end of the batch + */ +really_inline uint32_t json_structural_indexer::find_next_document_index(dom_parser_implementation &parser) { + // TODO don't count separately, just figure out depth + auto arr_cnt = 0; + auto obj_cnt = 0; + for (auto i = parser.n_structural_indexes - 1; i > 0; i--) { + auto idxb = parser.structural_indexes[i]; + switch (parser.buf[idxb]) { + case ':': + case ',': + continue; + case '}': + obj_cnt--; + continue; + case ']': + arr_cnt--; + continue; + case '{': + obj_cnt++; + break; + case '[': + arr_cnt++; + break; + } + auto idxa = parser.structural_indexes[i - 1]; + switch (parser.buf[idxa]) { + case '{': + case '[': + case ':': + case ',': + continue; + } + // Last document is complete, so the next document will appear after! + if (!arr_cnt && !obj_cnt) { + return parser.n_structural_indexes; + } + // Last document is incomplete; mark the document at i + 1 as the next one + return i; + } + return 0; +} + +// Skip the last character if it is partial +really_inline size_t json_structural_indexer::trim_partial_utf8(const uint8_t *buf, size_t len) { + if (unlikely(len < 3)) { + switch (len) { + case 2: + if (buf[len-1] >= 0b11000000) { return len-1; } // 2-, 3- and 4-byte characters with only 1 byte left + if (buf[len-2] >= 0b11100000) { return len-2; } // 3- and 4-byte characters with only 2 bytes left + return len; + case 1: + if (buf[len-1] >= 0b11000000) { return len-1; } // 2-, 3- and 4-byte characters with only 1 byte left + return len; + case 0: + return len; + } + } + if (buf[len-1] >= 0b11000000) { return len-1; } // 2-, 3- and 4-byte characters with only 1 byte left + if (buf[len-2] >= 0b11100000) { return len-2; } // 3- and 4-byte characters with only 1 byte left + if (buf[len-3] >= 0b11110000) { return len-3; } // 4-byte characters with only 3 bytes left + return len; +} + } // namespace stage1 From a5beffda785cb64aed7e97ac5fa65cf04ca858db Mon Sep 17 00:00:00 2001 From: John Keiser Date: Fri, 5 Jun 2020 10:44:21 -0700 Subject: [PATCH 2/8] Remove streaming_structural_parser.h --- src/arm64/dom_parser_implementation.cpp | 1 - src/fallback/dom_parser_implementation.cpp | 1 - .../stage2/streaming_structural_parser.h | 168 ----------------- src/generic/stage2/structural_parser.h | 169 ++++++++++++++++++ src/haswell/dom_parser_implementation.cpp | 1 - src/westmere/dom_parser_implementation.cpp | 1 - 6 files changed, 169 insertions(+), 172 deletions(-) delete mode 100755 src/generic/stage2/streaming_structural_parser.h diff --git a/src/arm64/dom_parser_implementation.cpp b/src/arm64/dom_parser_implementation.cpp index 9116965b1..8a41c6f06 100644 --- a/src/arm64/dom_parser_implementation.cpp +++ b/src/arm64/dom_parser_implementation.cpp @@ -108,7 +108,6 @@ namespace arm64 { #include "generic/stage2/atomparsing.h" #include "generic/stage2/structural_iterator.h" #include "generic/stage2/structural_parser.h" -#include "generic/stage2/streaming_structural_parser.h" WARN_UNUSED error_code dom_parser_implementation::parse(const uint8_t *_buf, size_t _len, dom::document &_doc) noexcept { error_code err = stage1(_buf, _len, false); diff --git a/src/fallback/dom_parser_implementation.cpp b/src/fallback/dom_parser_implementation.cpp index ae0944c88..61410973d 100644 --- a/src/fallback/dom_parser_implementation.cpp +++ b/src/fallback/dom_parser_implementation.cpp @@ -260,7 +260,6 @@ namespace fallback { #include "generic/stage2/atomparsing.h" #include "generic/stage2/structural_iterator.h" #include "generic/stage2/structural_parser.h" -#include "generic/stage2/streaming_structural_parser.h" WARN_UNUSED error_code dom_parser_implementation::parse(const uint8_t *_buf, size_t _len, dom::document &_doc) noexcept { error_code err = stage1(_buf, _len, false); diff --git a/src/generic/stage2/streaming_structural_parser.h b/src/generic/stage2/streaming_structural_parser.h deleted file mode 100755 index 8e63d0287..000000000 --- a/src/generic/stage2/streaming_structural_parser.h +++ /dev/null @@ -1,168 +0,0 @@ -namespace stage2 { - -struct streaming_structural_parser: structural_parser { - really_inline streaming_structural_parser(dom_parser_implementation &_parser) : structural_parser(_parser, _parser.next_structural_index) {} - - // override to add streaming - WARN_UNUSED really_inline error_code start(ret_address_t finish_parser) { - // If there are no structurals left, return EMPTY - if (structurals.at_end(parser.n_structural_indexes)) { - return parser.error = EMPTY; - } - - log_start(); - init(); - - // Capacity ain't no thang for streaming, so we don't check it. - // Advance to the first character as soon as possible - advance_char(); - // Push the root scope (there is always at least one scope) - if (start_document(finish_parser)) { - return parser.error = DEPTH_ERROR; - } - return SUCCESS; - } - - // override to add streaming - WARN_UNUSED really_inline error_code finish() { - if ( structurals.past_end(parser.n_structural_indexes) ) { - log_error("IMPOSSIBLE: past the end of the JSON!"); - return parser.error = TAPE_ERROR; - } - end_document(); - parser.next_structural_index = uint32_t(structurals.next_structural_index()); - if (depth != 0) { - log_error("Unclosed objects or arrays!"); - return parser.error = TAPE_ERROR; - } - if (parser.containing_scope[depth].tape_index != 0) { - log_error("IMPOSSIBLE: root scope tape index did not start at 0!"); - return parser.error = TAPE_ERROR; - } - return SUCCESS; - } -}; - -} // namespace stage2 - -/************ - * The JSON is parsed to a tape, see the accompanying tape.md file - * for documentation. - ***********/ -WARN_UNUSED error_code dom_parser_implementation::stage2_next(dom::document &_doc) noexcept { - this->doc = &_doc; - static constexpr stage2::unified_machine_addresses addresses = INIT_ADDRESSES(); - stage2::streaming_structural_parser parser(*this); - error_code result = parser.start(addresses.finish); - if (result) { return result; } - // - // Read first value - // - switch (parser.structurals.current_char()) { - case '{': - FAIL_IF( parser.start_object(addresses.finish) ); - goto object_begin; - case '[': - FAIL_IF( parser.start_array(addresses.finish) ); - goto array_begin; - case '"': - FAIL_IF( parser.parse_string() ); - goto finish; - case 't': case 'f': case 'n': - FAIL_IF( parser.parse_single_atom() ); - goto finish; - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - FAIL_IF( - parser.structurals.with_space_terminated_copy([&](const uint8_t *copy, size_t idx) { - return parser.parse_number(©[idx], false); - }) - ); - goto finish; - case '-': - FAIL_IF( - parser.structurals.with_space_terminated_copy([&](const uint8_t *copy, size_t idx) { - return parser.parse_number(©[idx], true); - }) - ); - goto finish; - default: - parser.log_error("Document starts with a non-value character"); - goto error; - } - -// -// Object parser parsers -// -object_begin: - switch (parser.advance_char()) { - case '"': { - FAIL_IF( parser.parse_string(true) ); - goto object_key_parser; - } - case '}': - parser.end_object(); - goto scope_end; - default: - parser.log_error("Object does not start with a key"); - goto error; - } - -object_key_parser: - if (parser.advance_char() != ':' ) { parser.log_error("Missing colon after key in object"); goto error; } - parser.increment_count(); - parser.advance_char(); - GOTO( parser.parse_value(addresses, addresses.object_continue) ); - -object_continue: - switch (parser.advance_char()) { - case ',': - if (parser.advance_char() != '"' ) { parser.log_error("Key string missing at beginning of field in object"); goto error; } - FAIL_IF( parser.parse_string(true) ); - goto object_key_parser; - case '}': - parser.end_object(); - goto scope_end; - default: - parser.log_error("No comma between object fields"); - goto error; - } - -scope_end: - CONTINUE( parser.parser.ret_address[parser.depth] ); - -// -// Array parser parsers -// -array_begin: - if (parser.advance_char() == ']') { - parser.end_array(); - goto scope_end; - } - parser.increment_count(); - -main_array_switch: - /* we call update char on all paths in, so we can peek at parser.c on the - * on paths that can accept a close square brace (post-, and at start) */ - GOTO( parser.parse_value(addresses, addresses.array_continue) ); - -array_continue: - switch (parser.advance_char()) { - case ',': - parser.increment_count(); - parser.advance_char(); - goto main_array_switch; - case ']': - parser.end_array(); - goto scope_end; - default: - parser.log_error("Missing comma between array values"); - goto error; - } - -finish: - return parser.finish(); - -error: - return parser.error(); -} diff --git a/src/generic/stage2/structural_parser.h b/src/generic/stage2/structural_parser.h index ce588fa98..272a8b927 100644 --- a/src/generic/stage2/structural_parser.h +++ b/src/generic/stage2/structural_parser.h @@ -504,3 +504,172 @@ finish: error: return parser.error(); } + +namespace stage2 { + +struct streaming_structural_parser: structural_parser { + really_inline streaming_structural_parser(dom_parser_implementation &_parser) : structural_parser(_parser, _parser.next_structural_index) {} + + // override to add streaming + WARN_UNUSED really_inline error_code start(ret_address_t finish_parser) { + // If there are no structurals left, return EMPTY + if (structurals.at_end(parser.n_structural_indexes)) { + return parser.error = EMPTY; + } + + log_start(); + init(); + + // Capacity ain't no thang for streaming, so we don't check it. + // Advance to the first character as soon as possible + advance_char(); + // Push the root scope (there is always at least one scope) + if (start_document(finish_parser)) { + return parser.error = DEPTH_ERROR; + } + return SUCCESS; + } + + // override to add streaming + WARN_UNUSED really_inline error_code finish() { + if ( structurals.past_end(parser.n_structural_indexes) ) { + log_error("IMPOSSIBLE: past the end of the JSON!"); + return parser.error = TAPE_ERROR; + } + end_document(); + parser.next_structural_index = uint32_t(structurals.next_structural_index()); + if (depth != 0) { + log_error("Unclosed objects or arrays!"); + return parser.error = TAPE_ERROR; + } + if (parser.containing_scope[depth].tape_index != 0) { + log_error("IMPOSSIBLE: root scope tape index did not start at 0!"); + return parser.error = TAPE_ERROR; + } + return SUCCESS; + } +}; + +} // namespace stage2 + +/************ + * The JSON is parsed to a tape, see the accompanying tape.md file + * for documentation. + ***********/ +WARN_UNUSED error_code dom_parser_implementation::stage2_next(dom::document &_doc) noexcept { + this->doc = &_doc; + static constexpr stage2::unified_machine_addresses addresses = INIT_ADDRESSES(); + stage2::streaming_structural_parser parser(*this); + error_code result = parser.start(addresses.finish); + if (result) { return result; } + // + // Read first value + // + switch (parser.structurals.current_char()) { + case '{': + FAIL_IF( parser.start_object(addresses.finish) ); + goto object_begin; + case '[': + FAIL_IF( parser.start_array(addresses.finish) ); + goto array_begin; + case '"': + FAIL_IF( parser.parse_string() ); + goto finish; + case 't': case 'f': case 'n': + FAIL_IF( parser.parse_single_atom() ); + goto finish; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + FAIL_IF( + parser.structurals.with_space_terminated_copy([&](const uint8_t *copy, size_t idx) { + return parser.parse_number(©[idx], false); + }) + ); + goto finish; + case '-': + FAIL_IF( + parser.structurals.with_space_terminated_copy([&](const uint8_t *copy, size_t idx) { + return parser.parse_number(©[idx], true); + }) + ); + goto finish; + default: + parser.log_error("Document starts with a non-value character"); + goto error; + } + +// +// Object parser parsers +// +object_begin: + switch (parser.advance_char()) { + case '"': { + FAIL_IF( parser.parse_string(true) ); + goto object_key_parser; + } + case '}': + parser.end_object(); + goto scope_end; + default: + parser.log_error("Object does not start with a key"); + goto error; + } + +object_key_parser: + if (parser.advance_char() != ':' ) { parser.log_error("Missing colon after key in object"); goto error; } + parser.increment_count(); + parser.advance_char(); + GOTO( parser.parse_value(addresses, addresses.object_continue) ); + +object_continue: + switch (parser.advance_char()) { + case ',': + if (parser.advance_char() != '"' ) { parser.log_error("Key string missing at beginning of field in object"); goto error; } + FAIL_IF( parser.parse_string(true) ); + goto object_key_parser; + case '}': + parser.end_object(); + goto scope_end; + default: + parser.log_error("No comma between object fields"); + goto error; + } + +scope_end: + CONTINUE( parser.parser.ret_address[parser.depth] ); + +// +// Array parser parsers +// +array_begin: + if (parser.advance_char() == ']') { + parser.end_array(); + goto scope_end; + } + parser.increment_count(); + +main_array_switch: + /* we call update char on all paths in, so we can peek at parser.c on the + * on paths that can accept a close square brace (post-, and at start) */ + GOTO( parser.parse_value(addresses, addresses.array_continue) ); + +array_continue: + switch (parser.advance_char()) { + case ',': + parser.increment_count(); + parser.advance_char(); + goto main_array_switch; + case ']': + parser.end_array(); + goto scope_end; + default: + parser.log_error("Missing comma between array values"); + goto error; + } + +finish: + return parser.finish(); + +error: + return parser.error(); +} diff --git a/src/haswell/dom_parser_implementation.cpp b/src/haswell/dom_parser_implementation.cpp index c5e6c2918..b6145178b 100644 --- a/src/haswell/dom_parser_implementation.cpp +++ b/src/haswell/dom_parser_implementation.cpp @@ -97,7 +97,6 @@ namespace haswell { #include "generic/stage2/atomparsing.h" #include "generic/stage2/structural_iterator.h" #include "generic/stage2/structural_parser.h" -#include "generic/stage2/streaming_structural_parser.h" WARN_UNUSED error_code dom_parser_implementation::parse(const uint8_t *_buf, size_t _len, dom::document &_doc) noexcept { error_code err = stage1(_buf, _len, false); diff --git a/src/westmere/dom_parser_implementation.cpp b/src/westmere/dom_parser_implementation.cpp index 376fe0f71..104005fc8 100644 --- a/src/westmere/dom_parser_implementation.cpp +++ b/src/westmere/dom_parser_implementation.cpp @@ -98,7 +98,6 @@ namespace westmere { #include "generic/stage2/atomparsing.h" #include "generic/stage2/structural_iterator.h" #include "generic/stage2/structural_parser.h" -#include "generic/stage2/streaming_structural_parser.h" WARN_UNUSED error_code dom_parser_implementation::parse(const uint8_t *_buf, size_t _len, dom::document &_doc) noexcept { error_code err = stage1(_buf, _len, false); From 5e69fb782aaa4a9cff50400b540674e66a48bad2 Mon Sep 17 00:00:00 2001 From: John Keiser Date: Fri, 5 Jun 2020 10:49:19 -0700 Subject: [PATCH 3/8] Call a function to parse structurals --- src/generic/stage2/structural_parser.h | 28 +++++++++++++++----------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/generic/stage2/structural_parser.h b/src/generic/stage2/structural_parser.h index 272a8b927..4721c8864 100644 --- a/src/generic/stage2/structural_parser.h +++ b/src/generic/stage2/structural_parser.h @@ -368,23 +368,17 @@ struct structural_parser { really_inline void log_error(const char *error) { logger::log_line(structurals, "", "ERROR", error); } -}; +}; // struct structural_parser // Redefine FAIL_IF to use goto since it'll be used inside the function now #undef FAIL_IF #define FAIL_IF(EXPR) { if (EXPR) { goto error; } } -} // namespace stage2 - -/************ - * The JSON is parsed to a tape, see the accompanying tape.md file - * for documentation. - ***********/ -WARN_UNUSED error_code dom_parser_implementation::stage2(dom::document &_doc) noexcept { - this->doc = &_doc; +WARN_UNUSED static error_code parse_structurals(dom_parser_implementation &dom_parser, dom::document &doc) noexcept { + dom_parser.doc = &doc; static constexpr stage2::unified_machine_addresses addresses = INIT_ADDRESSES(); - stage2::structural_parser parser(*this); - error_code result = parser.start(len, addresses.finish); + stage2::structural_parser parser(dom_parser); + error_code result = parser.start(dom_parser.len, addresses.finish); if (result) { return result; } // @@ -398,7 +392,7 @@ WARN_UNUSED error_code dom_parser_implementation::stage2(dom::document &_doc) no FAIL_IF( parser.start_array(addresses.finish) ); // Make sure the outer array is closed before continuing; otherwise, there are ways we could get // into memory corruption. See https://github.com/simdjson/simdjson/issues/906 - if (buf[structural_indexes[n_structural_indexes - 1]] != ']') { + if (parser.structurals.buf[parser.structurals.structural_indexes[dom_parser.n_structural_indexes - 1]] != ']') { goto error; } goto array_begin; @@ -505,6 +499,16 @@ error: return parser.error(); } +} // namespace stage2 + +/************ + * The JSON is parsed to a tape, see the accompanying tape.md file + * for documentation. + ***********/ +WARN_UNUSED error_code dom_parser_implementation::stage2(dom::document &_doc) noexcept { + return stage2::parse_structurals(*this, _doc); +} + namespace stage2 { struct streaming_structural_parser: structural_parser { From 059468b74e037776325de6c46a4d83f1d507fccd Mon Sep 17 00:00:00 2001 From: John Keiser Date: Fri, 5 Jun 2020 10:59:24 -0700 Subject: [PATCH 4/8] Eliminate streaming_structural_parser subclass with templates --- src/generic/stage2/structural_parser.h | 123 +++++++++++++------------ 1 file changed, 64 insertions(+), 59 deletions(-) diff --git a/src/generic/stage2/structural_parser.h b/src/generic/stage2/structural_parser.h index 4721c8864..036c57bef 100644 --- a/src/generic/stage2/structural_parser.h +++ b/src/generic/stage2/structural_parser.h @@ -75,7 +75,16 @@ struct structural_parser { uint8_t *current_string_buf_loc{}; uint32_t depth; - really_inline structural_parser(dom_parser_implementation &_parser, uint32_t next_structural = 0) : structurals(_parser.buf, _parser.len, _parser.structural_indexes.get(), next_structural), parser{_parser}, depth{0} {} + // For non-streaming, to pass an explicit 0 as next_structural, which enables optimizations + really_inline structural_parser(dom_parser_implementation &_parser, uint32_t next_structural) + : structurals(_parser.buf, _parser.len, _parser.structural_indexes.get(), next_structural), + parser{_parser}, + depth{0} { + } + // For streaming: pick up after the previous document + really_inline structural_parser(dom_parser_implementation &_parser) + : structural_parser(_parser, _parser.next_structural_index) { + } WARN_UNUSED really_inline bool start_scope(ret_address_t continue_state) { parser.containing_scope[depth].tape_index = parser.current_loc; @@ -263,13 +272,46 @@ struct structural_parser { } } + // override to add streaming WARN_UNUSED really_inline error_code finish() { - // the string might not be NULL terminated. - if ( !structurals.at_end(parser.n_structural_indexes) ) { - log_error("More than one JSON value at the root of the document, or extra characters at the end of the JSON!"); + if ( structurals.past_end(parser.n_structural_indexes) ) { + log_error("IMPOSSIBLE: past the end of the JSON!"); return parser.error = TAPE_ERROR; } end_document(); + if (depth != 0) { + log_error("Unclosed objects or arrays!"); + return parser.error = TAPE_ERROR; + } + if (parser.containing_scope[depth].tape_index != 0) { + log_error("IMPOSSIBLE: root scope tape index did not start at 0!"); + return parser.error = TAPE_ERROR; + } + return SUCCESS; + } + + template + WARN_UNUSED really_inline error_code finish() { + // Check if we're at (or past) the end + if (STREAMING) { + if ( structurals.past_end(parser.n_structural_indexes) ) { + log_error("IMPOSSIBLE: past the end of the JSON!"); + return parser.error = TAPE_ERROR; + } + } else { + // the string might not be NULL terminated. + if ( !structurals.at_end(parser.n_structural_indexes) ) { + log_error("More than one JSON value at the root of the document, or extra characters at the end of the JSON!"); + return parser.error = TAPE_ERROR; + } + } + + end_document(); + + if (STREAMING) { + parser.next_structural_index = uint32_t(structurals.next_structural_index()); + } + if (depth != 0) { log_error("Unclosed objects or arrays!"); return parser.error = TAPE_ERROR; @@ -328,11 +370,21 @@ struct structural_parser { parser.error = UNINITIALIZED; } + template WARN_UNUSED really_inline error_code start(size_t len, ret_address_t finish_state) { + if (STREAMING) { + // If there are no structurals left, return EMPTY + if (structurals.at_end(parser.n_structural_indexes)) { + return parser.error = EMPTY; + } + } + log_start(); init(); - if (len > parser.capacity()) { - return parser.error = CAPACITY; + if (!STREAMING) { + if (len > parser.capacity()) { + return parser.error = CAPACITY; + } } // Advance to the first character as soon as possible structurals.advance_char(); @@ -377,8 +429,8 @@ struct structural_parser { WARN_UNUSED static error_code parse_structurals(dom_parser_implementation &dom_parser, dom::document &doc) noexcept { dom_parser.doc = &doc; static constexpr stage2::unified_machine_addresses addresses = INIT_ADDRESSES(); - stage2::structural_parser parser(dom_parser); - error_code result = parser.start(dom_parser.len, addresses.finish); + stage2::structural_parser parser(dom_parser, 0); + error_code result = parser.start(dom_parser.len, addresses.finish); if (result) { return result; } // @@ -493,7 +545,7 @@ array_continue: } finish: - return parser.finish(); + return parser.finish(); error: return parser.error(); @@ -509,53 +561,6 @@ WARN_UNUSED error_code dom_parser_implementation::stage2(dom::document &_doc) no return stage2::parse_structurals(*this, _doc); } -namespace stage2 { - -struct streaming_structural_parser: structural_parser { - really_inline streaming_structural_parser(dom_parser_implementation &_parser) : structural_parser(_parser, _parser.next_structural_index) {} - - // override to add streaming - WARN_UNUSED really_inline error_code start(ret_address_t finish_parser) { - // If there are no structurals left, return EMPTY - if (structurals.at_end(parser.n_structural_indexes)) { - return parser.error = EMPTY; - } - - log_start(); - init(); - - // Capacity ain't no thang for streaming, so we don't check it. - // Advance to the first character as soon as possible - advance_char(); - // Push the root scope (there is always at least one scope) - if (start_document(finish_parser)) { - return parser.error = DEPTH_ERROR; - } - return SUCCESS; - } - - // override to add streaming - WARN_UNUSED really_inline error_code finish() { - if ( structurals.past_end(parser.n_structural_indexes) ) { - log_error("IMPOSSIBLE: past the end of the JSON!"); - return parser.error = TAPE_ERROR; - } - end_document(); - parser.next_structural_index = uint32_t(structurals.next_structural_index()); - if (depth != 0) { - log_error("Unclosed objects or arrays!"); - return parser.error = TAPE_ERROR; - } - if (parser.containing_scope[depth].tape_index != 0) { - log_error("IMPOSSIBLE: root scope tape index did not start at 0!"); - return parser.error = TAPE_ERROR; - } - return SUCCESS; - } -}; - -} // namespace stage2 - /************ * The JSON is parsed to a tape, see the accompanying tape.md file * for documentation. @@ -563,8 +568,8 @@ struct streaming_structural_parser: structural_parser { WARN_UNUSED error_code dom_parser_implementation::stage2_next(dom::document &_doc) noexcept { this->doc = &_doc; static constexpr stage2::unified_machine_addresses addresses = INIT_ADDRESSES(); - stage2::streaming_structural_parser parser(*this); - error_code result = parser.start(addresses.finish); + stage2::structural_parser parser(*this, next_structural_index); + error_code result = parser.start(len, addresses.finish); if (result) { return result; } // // Read first value @@ -672,7 +677,7 @@ array_continue: } finish: - return parser.finish(); + return parser.finish(); error: return parser.error(); From d731a7d52cc98545b76386b924bb77d6b32019bc Mon Sep 17 00:00:00 2001 From: John Keiser Date: Fri, 5 Jun 2020 11:09:53 -0700 Subject: [PATCH 5/8] Privatize structural_parser --- src/generic/stage2/structural_parser.h | 149 ++----------------------- 1 file changed, 8 insertions(+), 141 deletions(-) diff --git a/src/generic/stage2/structural_parser.h b/src/generic/stage2/structural_parser.h index 036c57bef..dc4575243 100644 --- a/src/generic/stage2/structural_parser.h +++ b/src/generic/stage2/structural_parser.h @@ -4,6 +4,7 @@ // "simdjson/stage2.h" (this simplifies amalgation) namespace stage2 { +namespace { // Make everything here private #ifdef SIMDJSON_USE_COMPUTED_GOTO #define INIT_ADDRESSES() { &&array_begin, &&array_continue, &&error, &&finish, &&object_begin, &&object_continue } @@ -81,10 +82,6 @@ struct structural_parser { parser{_parser}, depth{0} { } - // For streaming: pick up after the previous document - really_inline structural_parser(dom_parser_implementation &_parser) - : structural_parser(_parser, _parser.next_structural_index) { - } WARN_UNUSED really_inline bool start_scope(ret_address_t continue_state) { parser.containing_scope[depth].tape_index = parser.current_loc; @@ -272,24 +269,6 @@ struct structural_parser { } } - // override to add streaming - WARN_UNUSED really_inline error_code finish() { - if ( structurals.past_end(parser.n_structural_indexes) ) { - log_error("IMPOSSIBLE: past the end of the JSON!"); - return parser.error = TAPE_ERROR; - } - end_document(); - if (depth != 0) { - log_error("Unclosed objects or arrays!"); - return parser.error = TAPE_ERROR; - } - if (parser.containing_scope[depth].tape_index != 0) { - log_error("IMPOSSIBLE: root scope tape index did not start at 0!"); - return parser.error = TAPE_ERROR; - } - return SUCCESS; - } - template WARN_UNUSED really_inline error_code finish() { // Check if we're at (or past) the end @@ -426,11 +405,12 @@ struct structural_parser { #undef FAIL_IF #define FAIL_IF(EXPR) { if (EXPR) { goto error; } } +template WARN_UNUSED static error_code parse_structurals(dom_parser_implementation &dom_parser, dom::document &doc) noexcept { dom_parser.doc = &doc; static constexpr stage2::unified_machine_addresses addresses = INIT_ADDRESSES(); - stage2::structural_parser parser(dom_parser, 0); - error_code result = parser.start(dom_parser.len, addresses.finish); + stage2::structural_parser parser(dom_parser, STREAMING ? dom_parser.next_structural_index : 0); + error_code result = parser.start(dom_parser.len, addresses.finish); if (result) { return result; } // @@ -545,12 +525,13 @@ array_continue: } finish: - return parser.finish(); + return parser.finish(); error: return parser.error(); } +} // namespace {} } // namespace stage2 /************ @@ -558,7 +539,7 @@ error: * for documentation. ***********/ WARN_UNUSED error_code dom_parser_implementation::stage2(dom::document &_doc) noexcept { - return stage2::parse_structurals(*this, _doc); + return stage2::parse_structurals(*this, _doc); } /************ @@ -566,119 +547,5 @@ WARN_UNUSED error_code dom_parser_implementation::stage2(dom::document &_doc) no * for documentation. ***********/ WARN_UNUSED error_code dom_parser_implementation::stage2_next(dom::document &_doc) noexcept { - this->doc = &_doc; - static constexpr stage2::unified_machine_addresses addresses = INIT_ADDRESSES(); - stage2::structural_parser parser(*this, next_structural_index); - error_code result = parser.start(len, addresses.finish); - if (result) { return result; } - // - // Read first value - // - switch (parser.structurals.current_char()) { - case '{': - FAIL_IF( parser.start_object(addresses.finish) ); - goto object_begin; - case '[': - FAIL_IF( parser.start_array(addresses.finish) ); - goto array_begin; - case '"': - FAIL_IF( parser.parse_string() ); - goto finish; - case 't': case 'f': case 'n': - FAIL_IF( parser.parse_single_atom() ); - goto finish; - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - FAIL_IF( - parser.structurals.with_space_terminated_copy([&](const uint8_t *copy, size_t idx) { - return parser.parse_number(©[idx], false); - }) - ); - goto finish; - case '-': - FAIL_IF( - parser.structurals.with_space_terminated_copy([&](const uint8_t *copy, size_t idx) { - return parser.parse_number(©[idx], true); - }) - ); - goto finish; - default: - parser.log_error("Document starts with a non-value character"); - goto error; - } - -// -// Object parser parsers -// -object_begin: - switch (parser.advance_char()) { - case '"': { - FAIL_IF( parser.parse_string(true) ); - goto object_key_parser; - } - case '}': - parser.end_object(); - goto scope_end; - default: - parser.log_error("Object does not start with a key"); - goto error; - } - -object_key_parser: - if (parser.advance_char() != ':' ) { parser.log_error("Missing colon after key in object"); goto error; } - parser.increment_count(); - parser.advance_char(); - GOTO( parser.parse_value(addresses, addresses.object_continue) ); - -object_continue: - switch (parser.advance_char()) { - case ',': - if (parser.advance_char() != '"' ) { parser.log_error("Key string missing at beginning of field in object"); goto error; } - FAIL_IF( parser.parse_string(true) ); - goto object_key_parser; - case '}': - parser.end_object(); - goto scope_end; - default: - parser.log_error("No comma between object fields"); - goto error; - } - -scope_end: - CONTINUE( parser.parser.ret_address[parser.depth] ); - -// -// Array parser parsers -// -array_begin: - if (parser.advance_char() == ']') { - parser.end_array(); - goto scope_end; - } - parser.increment_count(); - -main_array_switch: - /* we call update char on all paths in, so we can peek at parser.c on the - * on paths that can accept a close square brace (post-, and at start) */ - GOTO( parser.parse_value(addresses, addresses.array_continue) ); - -array_continue: - switch (parser.advance_char()) { - case ',': - parser.increment_count(); - parser.advance_char(); - goto main_array_switch; - case ']': - parser.end_array(); - goto scope_end; - default: - parser.log_error("Missing comma between array values"); - goto error; - } - -finish: - return parser.finish(); - -error: - return parser.error(); + return stage2::parse_structurals(*this, _doc); } From 9dd6972d26425c2acc969257de289ab5bb8d3df8 Mon Sep 17 00:00:00 2001 From: John Keiser Date: Fri, 5 Jun 2020 11:22:06 -0700 Subject: [PATCH 6/8] Remove impossible checks, add EMPTY check to normal parser --- src/generic/stage2/structural_parser.h | 38 ++++++-------------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/src/generic/stage2/structural_parser.h b/src/generic/stage2/structural_parser.h index dc4575243..63dc5695f 100644 --- a/src/generic/stage2/structural_parser.h +++ b/src/generic/stage2/structural_parser.h @@ -271,34 +271,22 @@ struct structural_parser { template WARN_UNUSED really_inline error_code finish() { - // Check if we're at (or past) the end + end_document(); + if (STREAMING) { - if ( structurals.past_end(parser.n_structural_indexes) ) { - log_error("IMPOSSIBLE: past the end of the JSON!"); - return parser.error = TAPE_ERROR; - } + parser.next_structural_index = uint32_t(structurals.next_structural_index()); } else { - // the string might not be NULL terminated. + // Check if we're at the end or if there is stuff left still if ( !structurals.at_end(parser.n_structural_indexes) ) { log_error("More than one JSON value at the root of the document, or extra characters at the end of the JSON!"); return parser.error = TAPE_ERROR; } } - end_document(); - - if (STREAMING) { - parser.next_structural_index = uint32_t(structurals.next_structural_index()); - } - if (depth != 0) { log_error("Unclosed objects or arrays!"); return parser.error = TAPE_ERROR; } - if (parser.containing_scope[depth].tape_index != 0) { - log_error("IMPOSSIBLE: root scope tape index did not start at 0!"); - return parser.error = TAPE_ERROR; - } return SUCCESS; } @@ -349,22 +337,14 @@ struct structural_parser { parser.error = UNINITIALIZED; } - template - WARN_UNUSED really_inline error_code start(size_t len, ret_address_t finish_state) { - if (STREAMING) { - // If there are no structurals left, return EMPTY - if (structurals.at_end(parser.n_structural_indexes)) { - return parser.error = EMPTY; - } + WARN_UNUSED really_inline error_code start(ret_address_t finish_state) { + // If there are no structurals left, return EMPTY + if (structurals.at_end(parser.n_structural_indexes)) { + return parser.error = EMPTY; } log_start(); init(); - if (!STREAMING) { - if (len > parser.capacity()) { - return parser.error = CAPACITY; - } - } // Advance to the first character as soon as possible structurals.advance_char(); // Push the root scope (there is always at least one scope) @@ -410,7 +390,7 @@ WARN_UNUSED static error_code parse_structurals(dom_parser_implementation &dom_p dom_parser.doc = &doc; static constexpr stage2::unified_machine_addresses addresses = INIT_ADDRESSES(); stage2::structural_parser parser(dom_parser, STREAMING ? dom_parser.next_structural_index : 0); - error_code result = parser.start(dom_parser.len, addresses.finish); + error_code result = parser.start(addresses.finish); if (result) { return result; } // From 6f90f5dc5fbd9986a2e6716ad26e5c7453948f58 Mon Sep 17 00:00:00 2001 From: John Keiser Date: Fri, 5 Jun 2020 11:48:22 -0700 Subject: [PATCH 7/8] Remove templating from finish() method --- src/generic/stage2/logger.h | 6 ++++++ src/generic/stage2/structural_parser.h | 25 ++++++++++++------------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/generic/stage2/logger.h b/src/generic/stage2/logger.h index 85f2444ad..7d6fb97a0 100644 --- a/src/generic/stage2/logger.h +++ b/src/generic/stage2/logger.h @@ -30,6 +30,12 @@ namespace logger { } } + static really_inline void log_string(const char *message) { + if (LOG_ENABLED) { + printf("%s\n", message); + } + } + // Logs a single line of template static really_inline void log_line(S &structurals, const char *title_prefix, const char *title, const char *detail) { diff --git a/src/generic/stage2/structural_parser.h b/src/generic/stage2/structural_parser.h index 63dc5695f..4c112b67d 100644 --- a/src/generic/stage2/structural_parser.h +++ b/src/generic/stage2/structural_parser.h @@ -269,19 +269,9 @@ struct structural_parser { } } - template WARN_UNUSED really_inline error_code finish() { end_document(); - - if (STREAMING) { - parser.next_structural_index = uint32_t(structurals.next_structural_index()); - } else { - // Check if we're at the end or if there is stuff left still - if ( !structurals.at_end(parser.n_structural_indexes) ) { - log_error("More than one JSON value at the root of the document, or extra characters at the end of the JSON!"); - return parser.error = TAPE_ERROR; - } - } + parser.next_structural_index = uint32_t(structurals.next_structural_index()); if (depth != 0) { log_error("Unclosed objects or arrays!"); @@ -505,7 +495,7 @@ array_continue: } finish: - return parser.finish(); + return parser.finish(); error: return parser.error(); @@ -519,7 +509,16 @@ error: * for documentation. ***********/ WARN_UNUSED error_code dom_parser_implementation::stage2(dom::document &_doc) noexcept { - return stage2::parse_structurals(*this, _doc); + error_code result = stage2::parse_structurals(*this, _doc); + if (result) { return result; } + + // If we didn't make it to the end, it's an error + if ( next_structural_index != n_structural_indexes ) { + logger::log_string("More than one JSON value at the root of the document, or extra characters at the end of the JSON!"); + return error = TAPE_ERROR; + } + + return SUCCESS; } /************ From ea08e7d1922ef1bef1b7190afd84361e97253798 Mon Sep 17 00:00:00 2001 From: John Keiser Date: Tue, 9 Jun 2020 17:52:13 -0700 Subject: [PATCH 8/8] Remove unused extra copy of find_next_document_index --- src/generic/stage1/json_structural_indexer.h | 89 -------------------- 1 file changed, 89 deletions(-) diff --git a/src/generic/stage1/json_structural_indexer.h b/src/generic/stage1/json_structural_indexer.h index 4f43d24a4..6f80123e7 100644 --- a/src/generic/stage1/json_structural_indexer.h +++ b/src/generic/stage1/json_structural_indexer.h @@ -73,8 +73,6 @@ private: really_inline void step(const uint8_t *block, buf_block_reader &reader) noexcept; really_inline void next(simd::simd8x64 in, json_block block, size_t idx); really_inline error_code finish(dom_parser_implementation &parser, size_t idx, size_t len, bool partial); - static really_inline uint32_t find_next_document_index(dom_parser_implementation &parser); - static really_inline size_t trim_partial_utf8(const uint8_t *buf, size_t len); json_scanner scanner{}; utf8_checker checker{}; @@ -197,91 +195,4 @@ really_inline error_code json_structural_indexer::finish(dom_parser_implementati return checker.errors(); } -/** - * This algorithm is used to quickly identify the last structural position that - * makes up a complete document. - * - * It does this by going backwards and finding the last *document boundary* (a - * place where one value follows another without a comma between them). If the - * last document (the characters after the boundary) has an equal number of - * start and end brackets, it is considered complete. - * - * Simply put, we iterate over the structural characters, starting from - * the end. We consider that we found the end of a JSON document when the - * first element of the pair is NOT one of these characters: '{' '[' ';' ',' - * and when the second element is NOT one of these characters: '}' '}' ';' ','. - * - * This simple comparison works most of the time, but it does not cover cases - * where the batch's structural indexes contain a perfect amount of documents. - * In such a case, we do not have access to the structural index which follows - * the last document, therefore, we do not have access to the second element in - * the pair, and means that we cannot identify the last document. To fix this - * issue, we keep a count of the open and closed curly/square braces we found - * while searching for the pair. When we find a pair AND the count of open and - * closed curly/square braces is the same, we know that we just passed a - * complete - * document, therefore the last json buffer location is the end of the batch - */ -really_inline uint32_t json_structural_indexer::find_next_document_index(dom_parser_implementation &parser) { - // TODO don't count separately, just figure out depth - auto arr_cnt = 0; - auto obj_cnt = 0; - for (auto i = parser.n_structural_indexes - 1; i > 0; i--) { - auto idxb = parser.structural_indexes[i]; - switch (parser.buf[idxb]) { - case ':': - case ',': - continue; - case '}': - obj_cnt--; - continue; - case ']': - arr_cnt--; - continue; - case '{': - obj_cnt++; - break; - case '[': - arr_cnt++; - break; - } - auto idxa = parser.structural_indexes[i - 1]; - switch (parser.buf[idxa]) { - case '{': - case '[': - case ':': - case ',': - continue; - } - // Last document is complete, so the next document will appear after! - if (!arr_cnt && !obj_cnt) { - return parser.n_structural_indexes; - } - // Last document is incomplete; mark the document at i + 1 as the next one - return i; - } - return 0; -} - -// Skip the last character if it is partial -really_inline size_t json_structural_indexer::trim_partial_utf8(const uint8_t *buf, size_t len) { - if (unlikely(len < 3)) { - switch (len) { - case 2: - if (buf[len-1] >= 0b11000000) { return len-1; } // 2-, 3- and 4-byte characters with only 1 byte left - if (buf[len-2] >= 0b11100000) { return len-2; } // 3- and 4-byte characters with only 2 bytes left - return len; - case 1: - if (buf[len-1] >= 0b11000000) { return len-1; } // 2-, 3- and 4-byte characters with only 1 byte left - return len; - case 0: - return len; - } - } - if (buf[len-1] >= 0b11000000) { return len-1; } // 2-, 3- and 4-byte characters with only 1 byte left - if (buf[len-2] >= 0b11100000) { return len-2; } // 3- and 4-byte characters with only 1 byte left - if (buf[len-3] >= 0b11110000) { return len-3; } // 4-byte characters with only 3 bytes left - return len; -} - } // namespace stage1