From 910f27246786a528ce1d749c1e62c232b01b3aca Mon Sep 17 00:00:00 2001 From: John Keiser Date: Fri, 21 Feb 2020 13:34:27 -0800 Subject: [PATCH] Add parser implementation interface and selection API (#501) * Make architecture implementations virtual functions - Easier to add new architectures (add implementation to implementation.cpp) - Easier to add new algorithms / functions to architecture selection (add to implementation.h, implement) - Automatically select best implementation in static initialization - Allow user to explicitly select implementation with a string (i.e. parameter) - Allow user to inspect current implementation name/description - Allow user to list available implementations - Eliminate architecture enum and architecture-based templating - Add noexcept in non-inline functions * Move implementation static methods to their own classes * Detect best supported implementation on first use * available_implementationsI() -> available_implementations --- Makefile | 14 +- amalgamation.sh | 9 +- benchmark/benchfeatures.cpp | 19 +- benchmark/benchmarker.h | 39 +- benchmark/json_parser.h | 136 ----- benchmark/parse.cpp | 49 +- benchmark/statisticalmodel.cpp | 11 +- include/CMakeLists.txt | 6 +- include/simdjson/architecture.h | 30 - include/simdjson/document.h | 232 +++++++- include/simdjson/document_iterator.h | 484 +--------------- include/simdjson/document_parser.h | 597 -------------------- include/simdjson/error.h | 7 +- include/simdjson/implementation.h | 238 ++++++++ include/simdjson/inline/document.h | 192 +++++++ include/simdjson/inline/document_iterator.h | 493 ++++++++++++++++ include/simdjson/jsonparser.h | 54 ++ include/simdjson/jsonstream.h | 68 +-- include/simdjson/parsedjson.h | 2 + include/simdjson/parsedjsoniterator.h | 2 + include/simdjson/simdjson.h | 1 - include/simdjson/stage1_find_marks.h | 6 - include/simdjson/stage2_build_tape.h | 6 - singleheader/simdjson.cpp | 3 +- src/CMakeLists.txt | 6 +- src/arm64/implementation.h | 26 + src/arm64/stage1_find_marks.h | 12 +- src/arm64/stage2_build_tape.h | 18 +- src/document.cpp | 66 +++ src/document_parser.cpp | 111 ---- src/error.cpp | 22 +- src/generic/stage1_find_marks.h | 2 +- src/generic/stage2_build_tape.h | 19 +- src/generic/stage2_streaming_build_tape.h | 15 +- src/haswell/implementation.h | 30 + src/haswell/stage1_find_marks.h | 13 +- src/haswell/stage2_build_tape.h | 20 +- src/implementation.cpp | 83 +++ src/jsonparser.cpp | 92 --- src/westmere/implementation.h | 26 + src/westmere/stage1_find_marks.h | 13 +- src/westmere/stage2_build_tape.h | 21 +- tests/basictests.cpp | 2 +- 43 files changed, 1565 insertions(+), 1730 deletions(-) delete mode 100644 benchmark/json_parser.h delete mode 100644 include/simdjson/architecture.h delete mode 100644 include/simdjson/document_parser.h create mode 100644 include/simdjson/implementation.h create mode 100644 include/simdjson/inline/document.h create mode 100644 include/simdjson/inline/document_iterator.h delete mode 100755 include/simdjson/stage1_find_marks.h delete mode 100644 include/simdjson/stage2_build_tape.h create mode 100644 src/arm64/implementation.h delete mode 100644 src/document_parser.cpp create mode 100644 src/haswell/implementation.h create mode 100644 src/implementation.cpp delete mode 100644 src/jsonparser.cpp create mode 100644 src/westmere/implementation.h diff --git a/Makefile b/Makefile index 2d1fb0282..823f7836a 100644 --- a/Makefile +++ b/Makefile @@ -70,10 +70,10 @@ LIBHEADERS_HASWELL= src/haswell/bitmanipulation.h src/haswell/bitmask.h src/h LIBHEADERS_WESTMERE=src/westmere/bitmanipulation.h src/westmere/bitmask.h src/westmere/intrinsics.h src/westmere/numberparsing.h src/westmere/simd.h src/westmere/stage1_find_marks.h src/westmere/stage2_build_tape.h src/westmere/stringparsing.h LIBHEADERS=src/jsoncharutils.h src/simdprune_tables.h $(LIBHEADERS_GENERIC) $(LIBHEADERS_ARM64) $(LIBHEADERS_HASWELL) $(LIBHEADERS_WESTMERE) -PUBHEADERS=include/simdjson/common_defs.h include/simdjson/isadetection.h include/simdjson/jsonformatutils.h include/simdjson/jsonioutil.h include/simdjson/jsonminifier.h include/simdjson/jsonparser.h include/simdjson/padded_string.h include/simdjson/document.h include/simdjson/document_iterator.h include/simdjson/document_parser.h include/simdjson/parsedjson.h include/simdjson/jsonstream.h include/simdjson/portability.h include/simdjson/architecture.h include/simdjson/error.h include/simdjson/simdjson.h include/simdjson/simdjson_version.h include/simdjson/stage1_find_marks.h include/simdjson/stage2_build_tape.h +PUBHEADERS=include/simdjson/common_defs.h include/simdjson/isadetection.h include/simdjson/jsonformatutils.h include/simdjson/jsonioutil.h include/simdjson/jsonminifier.h include/simdjson/jsonparser.h include/simdjson/padded_string.h include/simdjson/document.h include/simdjson/inline/document.h include/simdjson/document_iterator.h include/simdjson/inline/document_iterator.h include/simdjson/implementation.h include/simdjson/parsedjson.h include/simdjson/jsonstream.h include/simdjson/portability.h include/simdjson/error.h include/simdjson/simdjson.h include/simdjson/simdjson_version.h HEADERS=$(PUBHEADERS) $(LIBHEADERS) -LIBFILES=src/jsonioutil.cpp src/jsonparser.cpp src/error.cpp src/stage1_find_marks.cpp src/stage2_build_tape.cpp src/document.cpp src/document_parser.cpp +LIBFILES=src/document.cpp src/error.cpp src/jsonioutil.cpp src/implementation.cpp src/stage1_find_marks.cpp src/stage2_build_tape.cpp MINIFIERHEADERS=include/simdjson/jsonminifier.h MINIFIERLIBFILES=src/jsonminifier.cpp @@ -160,16 +160,16 @@ submodules: $(JSON_INCLUDE) $(SAJSON_INCLUDE) $(RAPIDJSON_INCLUDE) $(JSON11_INCLUDE) $(FASTJSON_INCLUDE) $(GASON_INCLUDE) $(UJSON4C_INCLUDE) $(CJSON_INCLUDE) $(JSMN_INCLUDE) : submodules -parse: benchmark/parse.cpp benchmark/json_parser.h benchmark/event_counter.h benchmark/benchmarker.h $(HEADERS) $(LIBFILES) +parse: benchmark/parse.cpp benchmark/event_counter.h benchmark/benchmarker.h $(HEADERS) $(LIBFILES) $(CXX) $(CXXFLAGS) -o parse $(LIBFILES) benchmark/parse.cpp $(LIBFLAGS) get_corpus_benchmark: benchmark/get_corpus_benchmark.cpp $(HEADERS) $(LIBFILES) $(CXX) $(CXXFLAGS) -o get_corpus_benchmark $(LIBFILES) benchmark/get_corpus_benchmark.cpp $(LIBFLAGS) -parse_stream: benchmark/parse_stream.cpp benchmark/json_parser.h benchmark/event_counter.h benchmark/benchmarker.h $(HEADERS) $(LIBFILES) +parse_stream: benchmark/parse_stream.cpp benchmark/event_counter.h benchmark/benchmarker.h $(HEADERS) $(LIBFILES) $(CXX) $(CXXFLAGS) -o parse_stream $(LIBFILES) benchmark/parse_stream.cpp $(LIBFLAGS) -benchfeatures: benchmark/benchfeatures.cpp benchmark/json_parser.h benchmark/event_counter.h benchmark/benchmarker.h $(HEADERS) $(LIBFILES) +benchfeatures: benchmark/benchfeatures.cpp benchmark/event_counter.h benchmark/benchmarker.h $(HEADERS) $(LIBFILES) $(CXX) $(CXXFLAGS) -o benchfeatures $(LIBFILES) benchmark/benchfeatures.cpp $(LIBFLAGS) perfdiff: benchmark/perfdiff.cpp @@ -210,7 +210,7 @@ readme_examples: tests/readme_examples.cpp $(HEADERS) $(LIBFILES) numberparsingcheck:tests/numberparsingcheck.cpp $(HEADERS) $(LIBFILES) - $(CXX) $(CXXFLAGS) -o numberparsingcheck src/jsonioutil.cpp src/jsonparser.cpp src/error.cpp src/stage1_find_marks.cpp src/document.cpp src/document_parser.cpp tests/numberparsingcheck.cpp -I. $(LIBFLAGS) -DJSON_TEST_NUMBERS + $(CXX) $(CXXFLAGS) -o numberparsingcheck src/jsonioutil.cpp src/implementation.cpp src/error.cpp src/stage1_find_marks.cpp src/document.cpp tests/numberparsingcheck.cpp -I. $(LIBFLAGS) -DJSON_TEST_NUMBERS integer_tests:tests/integer_tests.cpp $(HEADERS) $(LIBFILES) $(CXX) $(CXXFLAGS) -o integer_tests $(LIBFILES) tests/integer_tests.cpp -I. $(LIBFLAGS) @@ -218,7 +218,7 @@ integer_tests:tests/integer_tests.cpp $(HEADERS) $(LIBFILES) stringparsingcheck:tests/stringparsingcheck.cpp $(HEADERS) $(LIBFILES) - $(CXX) $(CXXFLAGS) -o stringparsingcheck src/jsonioutil.cpp src/jsonparser.cpp src/error.cpp src/stage1_find_marks.cpp src/document.cpp src/document_parser.cpp tests/stringparsingcheck.cpp -I. $(LIBFLAGS) -DJSON_TEST_STRINGS + $(CXX) $(CXXFLAGS) -o stringparsingcheck src/jsonioutil.cpp src/implementation.cpp src/error.cpp src/stage1_find_marks.cpp src/document.cpp tests/stringparsingcheck.cpp -I. $(LIBFLAGS) -DJSON_TEST_STRINGS pointercheck:tests/pointercheck.cpp $(HEADERS) $(LIBFILES) $(CXX) $(CXXFLAGS) -o pointercheck $(LIBFILES) tests/pointercheck.cpp -I. $(LIBFLAGS) diff --git a/amalgamation.sh b/amalgamation.sh index 469f12dfb..3833ade00 100755 --- a/amalgamation.sh +++ b/amalgamation.sh @@ -16,14 +16,13 @@ INCLUDEPATH="$SCRIPTPATH/include" # this list excludes the "src/generic headers" ALLCFILES=" +document.cpp error.cpp +implementation.cpp jsonioutil.cpp jsonminifier.cpp -jsonparser.cpp stage1_find_marks.cpp stage2_build_tape.cpp -document.cpp -document_parser.cpp " # order matters @@ -38,11 +37,7 @@ simdjson/padded_string.h simdjson/jsonioutil.h simdjson/jsonminifier.h simdjson/document.h -simdjson/document_iterator.h -simdjson/document_parser.h simdjson/parsedjson.h -simdjson/stage1_find_marks.h -simdjson/stage2_build_tape.h simdjson/jsonparser.h simdjson/jsonstream.h " diff --git a/benchmark/benchfeatures.cpp b/benchmark/benchfeatures.cpp index 259c3f53c..8a2ba88b3 100644 --- a/benchmark/benchfeatures.cpp +++ b/benchmark/benchfeatures.cpp @@ -1,4 +1,3 @@ -#include "json_parser.h" #include "event_counter.h" #include @@ -34,10 +33,7 @@ #include "simdjson/common_defs.h" #include "simdjson/isadetection.h" #include "simdjson/jsonioutil.h" -#include "simdjson/jsonparser.h" -#include "simdjson/parsedjson.h" -#include "simdjson/stage1_find_marks.h" -#include "simdjson/stage2_build_tape.h" +#include "simdjson/document.h" #include @@ -132,6 +128,7 @@ struct option_struct { if (arch == architecture::UNSUPPORTED) { arch = find_best_supported_architecture(); } + document::parser::use_implementation(arch); } template @@ -160,7 +157,7 @@ struct feature_benchmarker { benchmarker struct23; benchmarker struct23_miss; - feature_benchmarker(json_parser& parser, event_collector& collector) : + feature_benchmarker(const simdjson::implementation &parser, event_collector& collector) : utf8 ("jsonexamples/generated/utf-8.json", parser, collector), utf8_miss ("jsonexamples/generated/utf-8-miss.json", parser, collector), escape ("jsonexamples/generated/escape.json", parser, collector), @@ -410,12 +407,10 @@ int main(int argc, char *argv[]) { event_collector collector; // Set up benchmarkers by reading all files - json_parser parser(options.arch); - - feature_benchmarker features(parser, collector); - benchmarker gsoc_2018("jsonexamples/gsoc-2018.json", parser, collector); - benchmarker twitter("jsonexamples/twitter.json", parser, collector); - benchmarker random("jsonexamples/random.json", parser, collector); + feature_benchmarker features(collector); + benchmarker gsoc_2018("jsonexamples/gsoc-2018.json", collector); + benchmarker twitter("jsonexamples/twitter.json", collector); + benchmarker random("jsonexamples/random.json", collector); // Run the benchmarks progress_bar progress(options.iterations, 100); diff --git a/benchmark/benchmarker.h b/benchmark/benchmarker.h index 36647fc52..4b2eb87d9 100644 --- a/benchmark/benchmarker.h +++ b/benchmark/benchmarker.h @@ -1,7 +1,6 @@ #ifndef __BENCHMARKER_H #define __BENCHMARKER_H -#include "json_parser.h" #include "event_counter.h" #include @@ -38,8 +37,6 @@ #include "simdjson/jsonioutil.h" #include "simdjson/jsonparser.h" #include "simdjson/document.h" -#include "simdjson/stage1_find_marks.h" -#include "simdjson/stage2_build_tape.h" #include @@ -264,8 +261,6 @@ struct benchmarker { const padded_string json; // JSON filename const char *filename; - // Parser that will parse the JSON file - const json_parser& parser; // Event collector that can be turned on to measure cycles, missed branches, etc. event_collector& collector; @@ -281,8 +276,8 @@ struct benchmarker { // Speed and event summary for allocation event_aggregate allocate_stage; - benchmarker(const char *_filename, const json_parser& _parser, event_collector& _collector) - : json(load_json(_filename)), filename(_filename), parser(_parser), collector(_collector), stats(NULL) {} + benchmarker(const char *_filename, event_collector& _collector) + : json(load_json(_filename)), filename(_filename), collector(_collector), stats(NULL) {} ~benchmarker() { if (stats) { @@ -307,14 +302,15 @@ struct benchmarker { really_inline void run_iteration(bool stage1_only, bool hotbuffers=false) { // Allocate document::parser collector.start(); - document::parser pj; - bool allocok = pj.allocate_capacity(json.size()); + document::parser parser; + bool allocok = parser.allocate_capacity(json.size()); event_count allocate_count = collector.end(); allocate_stage << allocate_count; + // Run it once to get hot buffers if(hotbuffers) { - int result = parser.parse((const uint8_t *)json.data(), json.size(), pj); - if (result != simdjson::SUCCESS) { - exit_error(string("Failed to parse ") + filename + string(":") + pj.get_error_message()); + auto result = parser.parse((const uint8_t *)json.data(), json.size()); + if (result.error) { + exit_error(string("Failed to parse ") + filename + string(":") + result.get_error_message()); } } @@ -325,12 +321,11 @@ struct benchmarker { // Stage 1 (find structurals) collector.start(); - int result = parser.stage1((const uint8_t *)json.data(), json.size(), pj); + error_code error = active_implementation->stage1((const uint8_t *)json.data(), json.size(), parser, false); event_count stage1_count = collector.end(); stage1 << stage1_count; - - if (result != simdjson::SUCCESS) { - exit_error(string("Failed to parse ") + filename + " during stage 1: " + pj.get_error_message()); + if (error) { + exit_error(string("Failed to parse ") + filename + " during stage 1: " + error_message(error)); } // Stage 2 (unified machine) and the rest @@ -340,9 +335,9 @@ struct benchmarker { } else { event_count stage2_count; collector.start(); - result = parser.stage2((const uint8_t *)json.data(), json.size(), pj); - if (result != simdjson::SUCCESS) { - exit_error(string("Failed to parse ") + filename + " during stage 2 parsing " + pj.get_error_message()); + error = active_implementation->stage2((const uint8_t *)json.data(), json.size(), parser); + if (error) { + exit_error(string("Failed to parse ") + filename + " during stage 2 parsing " + error_message(error)); } stage2_count = collector.end(); stage2 << stage2_count; @@ -351,12 +346,12 @@ struct benchmarker { // Calculate stats the first time we parse if (stats == NULL) { if (stage1_only) { // we need stage 2 once - result = parser.stage2((const uint8_t *)json.data(), json.size(), pj); - if (result != simdjson::SUCCESS) { + error = active_implementation->stage2((const uint8_t *)json.data(), json.size(), parser); + if (error) { printf("Warning: failed to parse during stage 2. Unable to acquire statistics.\n"); } } - stats = new json_stats(json, pj); + stats = new json_stats(json, parser); } } diff --git a/benchmark/json_parser.h b/benchmark/json_parser.h deleted file mode 100644 index c5f9a1eab..000000000 --- a/benchmark/json_parser.h +++ /dev/null @@ -1,136 +0,0 @@ -#ifndef __JSON_PARSER_H -#define __JSON_PARSER_H - -#include -#include -#ifndef _MSC_VER -#include -#include -#endif -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "linux-perf-events.h" -#ifdef __linux__ -#include -#endif -//#define DEBUG -#include "simdjson/common_defs.h" -#include "simdjson/isadetection.h" -#include "simdjson/jsonioutil.h" -#include "simdjson/jsonparser.h" -#include "simdjson/parsedjson.h" -#include "simdjson/stage1_find_marks.h" -#include "simdjson/stage2_build_tape.h" - -using namespace simdjson; -using std::string; - -using stage2_functype = int(const uint8_t *buf, size_t len, ParsedJson &pj); -using stage1_functype = int(const uint8_t *buf, size_t len, ParsedJson &pj); -using jsonparse_functype = int(const uint8_t *buf, size_t len, ParsedJson &pj, bool streaming); - -stage1_functype* get_stage1_func(const architecture arch) { - switch (arch) { - #ifdef IS_X86_64 - case architecture::HASWELL: - return &find_structural_bits; - case architecture::WESTMERE: - return &find_structural_bits; - #endif - #ifdef IS_ARM64 - case architecture::ARM64: - return &find_structural_bits; - #endif - default: - std::cerr << "The processor is not supported by simdjson." << std::endl; - exit(EXIT_FAILURE); - } -} - -stage2_functype* get_stage2_func(const architecture arch) { - switch (arch) { -#ifdef IS_X86_64 - case architecture::HASWELL: - return &unified_machine; - break; - case architecture::WESTMERE: - return &unified_machine; - break; -#endif -#ifdef IS_ARM64 - case architecture::ARM64: - return &unified_machine; - break; -#endif - default: - std::cerr << "The processor is not supported by simdjson." << std::endl; - exit(EXIT_FAILURE); - } -} - -jsonparse_functype* get_jsonparse_func(const architecture arch) { - switch (arch) { -#ifdef IS_X86_64 - case architecture::HASWELL: - return &json_parse_implementation; - break; - case architecture::WESTMERE: - return &json_parse_implementation; - break; -#endif -#ifdef IS_ARM64 - case architecture::ARM64: - return &json_parse_implementation; - break; -#endif - default: - std::cerr << "The processor is not supported by simdjson." << std::endl; - exit(EXIT_FAILURE); - } -} - -struct json_parser { - const architecture arch; - stage1_functype *stage1_func; - stage2_functype *stage2_func; - jsonparse_functype *jsonparse_func; - - json_parser(const architecture _arch) : arch(_arch) { - this->stage1_func = get_stage1_func(arch); - this->stage2_func = get_stage2_func(arch); - this->jsonparse_func = get_jsonparse_func(arch); - } - json_parser() : json_parser(find_best_supported_architecture()) {} - - int stage1(const uint8_t *buf, const size_t len, ParsedJson &pj) const { - return this->stage1_func(buf, len, pj); - } - - int stage2(const uint8_t *buf, const size_t len, ParsedJson &pj) const { - return this->stage2_func(buf, len, pj); - } - - int parse(const uint8_t *buf, const size_t len, ParsedJson &pj) const { - // yes, you can construct jsonparse from stage 1 and stage 2, - // but why emulate it when we have the real thing? - return this->jsonparse_func(buf, len, pj, false); - } -}; - -#endif \ No newline at end of file diff --git a/benchmark/parse.cpp b/benchmark/parse.cpp index d344cc32e..6508bb874 100644 --- a/benchmark/parse.cpp +++ b/benchmark/parse.cpp @@ -1,4 +1,3 @@ -#include "json_parser.h" #include "event_counter.h" #include @@ -33,10 +32,7 @@ #include "simdjson/common_defs.h" #include "simdjson/isadetection.h" #include "simdjson/jsonioutil.h" -#include "simdjson/jsonparser.h" -#include "simdjson/parsedjson.h" -#include "simdjson/stage1_find_marks.h" -#include "simdjson/stage2_build_tape.h" +#include "simdjson/document.h" #include @@ -63,17 +59,18 @@ void print_usage(ostream& out) { out << endl; out << "Options:" << endl; out << endl; - out << "-n # - Number of iterations per file. Default: 200" << endl; - out << "-i # - Number of times to iterate a single file before moving to the next. Default: 20" << endl; - out << "-t - Tabbed data output" << endl; - out << "-v - Verbose output." << endl; - out << "-s STAGE - Stop after the given stage." << endl; - out << " -s stage1 - Stop after find_structural_bits." << endl; - out << " -s all - Run all stages." << endl; - out << "-H - Make the buffers hot (reduce page allocation during parsing)" << endl; - - out << "-a ARCH - Use the parser with the designated architecture (HASWELL, WESTMERE" << endl; - out << " or ARM64). By default, detects best supported architecture." << endl; + out << "-n # - Number of iterations per file. Default: 200" << endl; + out << "-i # - Number of times to iterate a single file before moving to the next. Default: 20" << endl; + out << "-t - Tabbed data output" << endl; + out << "-v - Verbose output." << endl; + out << "-s stage1 - Stop after find_structural_bits." << endl; + out << "-s all - Run all stages." << endl; + out << "-H - Make the buffers hot (reduce page allocation during parsing)" << endl; + out << "-a IMPL - Use the given parser implementation. By default, detects the most advanced" << endl; + out << " implementation supported on the host machine." << endl; + for (auto impl : simdjson::available_implementations) { + out << "-a " << std::left << std::setw(9) << impl->name() << " - Use the " << impl->description() << " parser implementation." << endl; + } } void exit_usage(string message) { @@ -85,7 +82,6 @@ void exit_usage(string message) { struct option_struct { vector files; - architecture arch = architecture::UNSUPPORTED; bool stage1_only = false; int32_t iterations = 200; @@ -113,12 +109,14 @@ struct option_struct { case 'v': verbose = true; break; - case 'a': - arch = parse_architecture(optarg); - if (arch == architecture::UNSUPPORTED) { - exit_usage(string("Unsupported option value -a ") + optarg + ": expected -a HASWELL, WESTMERE or ARM64"); + case 'a': { + const implementation *impl = simdjson::available_implementations[optarg]; + if (!impl) { + exit_usage(string("Unsupported option value -a ") + optarg + ": expected -a haswell, westmere or arm64"); } + simdjson::active_implementation = impl; break; + } case 'H': hotbuffers = true; break; @@ -142,11 +140,6 @@ struct option_struct { int optind = 1; #endif - // If architecture is not specified, pick the best supported architecture by default - if (arch == architecture::UNSUPPORTED) { - arch = find_best_supported_architecture(); - } - // All remaining arguments are considered to be files for (int i=optind; iname() << endl; } // Start collecting events. We put this early so if it prints an error message, it's the @@ -186,10 +180,9 @@ int main(int argc, char *argv[]) { } // Set up benchmarkers by reading all files - json_parser parser(options.arch); vector benchmarkers; for (size_t i=0; i(p.data(), p.size(), pj) == - simdjson::SUCCESS); + bool isok = (stage_parser.stage1((const uint8_t *)p.data(), p.size(), parser, false) == simdjson::SUCCESS); unified.end(results); cy1 += results[0]; cl1 += results[1]; unified.start(); - isok = - isok && (simdjson::SUCCESS == unified_machine(p.data(), p.size(), pj)); + isok = isok && (stage_parser.stage2((const uint8_t *)p.data(), p.size(), parser) == simdjson::SUCCESS); unified.end(results); cy2 += results[0]; diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index 4770a82f8..b072e1dba 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -2,8 +2,10 @@ set(SIMDJSON_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include) set(SIMDJSON_INCLUDE ${SIMDJSON_INCLUDE_DIR}/simdjson/common_defs.h ${SIMDJSON_INCLUDE_DIR}/simdjson/document.h + ${SIMDJSON_INCLUDE_DIR}/simdjson/inline/document.h ${SIMDJSON_INCLUDE_DIR}/simdjson/document_iterator.h - ${SIMDJSON_INCLUDE_DIR}/simdjson/document_parser.h + ${SIMDJSON_INCLUDE_DIR}/simdjson/inline/document_iterator.h + ${SIMDJSON_INCLUDE_DIR}/simdjson/implementation.h ${SIMDJSON_INCLUDE_DIR}/simdjson/isadetection.h ${SIMDJSON_INCLUDE_DIR}/simdjson/jsonformatutils.h ${SIMDJSON_INCLUDE_DIR}/simdjson/jsonioutil.h @@ -14,6 +16,4 @@ set(SIMDJSON_INCLUDE ${SIMDJSON_INCLUDE_DIR}/simdjson/portability.h ${SIMDJSON_INCLUDE_DIR}/simdjson/simdjson.h ${SIMDJSON_INCLUDE_DIR}/simdjson/simdjson_version.h - ${SIMDJSON_INCLUDE_DIR}/simdjson/stage1_find_marks.h - ${SIMDJSON_INCLUDE_DIR}/simdjson/stage2_build_tape.h ) \ No newline at end of file diff --git a/include/simdjson/architecture.h b/include/simdjson/architecture.h deleted file mode 100644 index 8df6ce7db..000000000 --- a/include/simdjson/architecture.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef SIMDJSON_ARCHITECTURE_H -#define SIMDJSON_ARCHITECTURE_H - -namespace simdjson { - -// Represents the minimal architecture that would support an implementation -enum class architecture { - UNSUPPORTED, - WESTMERE, - HASWELL, - ARM64, -// TODO remove 'native' in favor of runtime dispatch? -// the 'native' enum class value should point at a good default on the current -// machine -#ifdef IS_X86_64 - NATIVE = WESTMERE -#elif defined(IS_ARM64) - NATIVE = ARM64 -#endif -}; - -architecture find_best_supported_architecture(); -architecture parse_architecture(char *arch_name); - -// backcompat -using Architecture = architecture; - -} // namespace simdjson - -#endif // SIMDJSON_ARCHITECTURE_H diff --git a/include/simdjson/document.h b/include/simdjson/document.h index 60fc7a83b..563e7a691 100644 --- a/include/simdjson/document.h +++ b/include/simdjson/document.h @@ -3,6 +3,7 @@ #include #include +#include #include "simdjson/common_defs.h" #include "simdjson/simdjson.h" #include "simdjson/padded_string.h" @@ -27,6 +28,7 @@ public: document &operator=(document &&o) = default; document &operator=(const document &o) = delete; + // Nested classes. See definitions later in file. using iterator = document_iterator; class parser; class doc_result; @@ -55,17 +57,19 @@ public: // // Throws invalid_json if the JSON is invalid. // - static doc_result parse(const uint8_t *buf, size_t len, bool realloc_if_needed = true); - static doc_result parse(const char *buf, size_t len, bool realloc_if_needed = true); - static doc_result parse(const std::string &s, bool realloc_if_needed = true); - static doc_result parse(const padded_string &s); + static doc_result parse(const uint8_t *buf, size_t len, bool realloc_if_needed = true) noexcept; + static doc_result parse(const char *buf, size_t len, bool realloc_if_needed = true) noexcept; + static doc_result parse(const std::string &s, bool realloc_if_needed = true) noexcept; + static doc_result parse(const padded_string &s) noexcept; + // We do not want to allow implicit conversion from C string to std::string. + doc_result parse(const char *buf, bool realloc_if_needed = true) noexcept = delete; std::unique_ptr tape; std::unique_ptr string_buf;// should be at least byte_capacity private: bool set_capacity(size_t len); -}; +}; // class document class document::doc_result { private: @@ -85,8 +89,18 @@ public: } document doc; error_code error; -}; + const std::string &get_error_message() { + return error_message(error); + } +}; // class doc_result +/** + * The result of document::parser::parse(). Stores an error code and a document reference. + * + * Designed so that you can either check the error code before using the document, or use + * exceptions and use thedirectly and parse it, or + * + */ class document::doc_ref_result { public: doc_ref_result(document &_doc, error_code _error) : doc(_doc), error(_error) { } @@ -101,12 +115,214 @@ public: } document& doc; error_code error; -}; + const std::string &get_error_message() noexcept { + return error_message(error); + } +}; // class document::doc_result +/** + * A persistent document parser. + * + * Use this if you intend to parse more than one document. It holds the internal memory necessary + * to do parsing, as well as memory for a single document that is overwritten on each parse. + * + * @note This is not thread safe: one parser cannot produce two documents at the same time! + */ +class document::parser { +public: + /** + * Create a JSON parser with zero capacity. Call allocate_capacity() to initialize it. + */ + parser()=default; + ~parser()=default; + + // this is a move only class + parser(document::parser &&p) = default; + parser(const document::parser &p) = delete; + parser &operator=(document::parser &&o) = default; + parser &operator=(const document::parser &o) = delete; + + // + // Parse a JSON document and return a reference to it. + // + // The JSON document still lives in the parser: this is the most efficient way to parse JSON + // documents because it reuses the same buffers, but you *must* use the document before you + // destroy the parser or call parse() again. + // + // Throws invalid_json if the JSON is invalid. + // + inline doc_ref_result parse(const uint8_t *buf, size_t len, bool realloc_if_needed = true) noexcept; + inline doc_ref_result parse(const char *buf, size_t len, bool realloc_if_needed = true) noexcept; + inline doc_ref_result parse(const std::string &s, bool realloc_if_needed = true) noexcept; + inline doc_ref_result parse(const padded_string &s) noexcept; + // We do not want to allow implicit conversion from C string to std::string. + doc_ref_result parse(const char *buf, bool realloc_if_needed = true) noexcept = delete; + + // + // Current capacity: the largest document this parser can support without reallocating. + // + size_t capacity() { return _capacity; } + + // + // The maximum level of nested object and arrays supported by this parser. + // + size_t max_depth() { return _max_depth; } + + // if needed, allocate memory so that the object is able to process JSON + // documents having up to capacity bytes and max_depth "depth" + WARN_UNUSED bool allocate_capacity(size_t capacity, size_t max_depth = DEFAULT_MAX_DEPTH) { + return set_capacity(capacity) && set_max_depth(max_depth); + } + + // type aliases for backcompat + using Iterator = document::iterator; + using InvalidJSON = invalid_json; + + // Next location to write to in the tape + uint32_t current_loc{0}; + + // structural indices passed from stage 1 to stage 2 + uint32_t n_structural_indexes{0}; + std::unique_ptr structural_indexes; + + // location and return address of each open { or [ + std::unique_ptr containing_scope_offset; +#ifdef SIMDJSON_USE_COMPUTED_GOTO + std::unique_ptr ret_address; +#else + std::unique_ptr ret_address; +#endif + + // Next place to write a string + uint8_t *current_string_buf_loc; + + bool valid{false}; + error_code error{UNINITIALIZED}; + + // Document we're writing to + document doc; + + // + // TODO these are deprecated; use the results of parse instead. + // + + // returns true if the document parsed was valid + bool is_valid() const { return valid; } + + // return an error code corresponding to the last parsing attempt, see + // simdjson.h will return UNITIALIZED if no parsing was attempted + int get_error_code() const { return error; } + + // return the string equivalent of "get_error_code" + std::string get_error_message() const { return error_message(error); } + + // print the json to std::ostream (should be valid) + // return false if the tape is likely wrong (e.g., you did not parse a valid + // JSON). + WARN_UNUSED + inline bool print_json(std::ostream &os) const { return is_valid() ? doc.print_json(os) : false; } + WARN_UNUSED + inline bool dump_raw_tape(std::ostream &os) const { return is_valid() ? doc.dump_raw_tape(os) : false; } + + // + // Parser callbacks: these are internal! + // + // TODO find a way to do this without exposing the interface or crippling performance + // + + // this should be called when parsing (right before writing the tapes) + really_inline void init_stage2(); + really_inline error_code on_error(error_code new_error_code); + really_inline error_code on_success(error_code success_code); + really_inline bool on_start_document(uint32_t depth); + really_inline bool on_start_object(uint32_t depth); + really_inline bool on_start_array(uint32_t depth); + // TODO we're not checking this bool + really_inline bool on_end_document(uint32_t depth); + really_inline bool on_end_object(uint32_t depth); + really_inline bool on_end_array(uint32_t depth); + really_inline bool on_true_atom(); + really_inline bool on_false_atom(); + really_inline bool on_null_atom(); + really_inline uint8_t *on_start_string(); + really_inline bool on_end_string(uint8_t *dst); + really_inline bool on_number_s64(int64_t value); + really_inline bool on_number_u64(uint64_t value); + really_inline bool on_number_double(double value); + // + // Called before a parse is initiated. + // + // - Returns CAPACITY if the document is too large + // - Returns MEMALLOC if we needed to allocate memory and could not + // + WARN_UNUSED really_inline error_code init_parse(size_t len); + + const document &get_document() const noexcept(false) { + if (!is_valid()) { + throw invalid_json(error); + } + return doc; + } + +private: + // + // The maximum document length this parser supports. + // + // Buffers are large enough to handle any document up to this length. + // + size_t _capacity{0}; + + // + // The maximum depth (number of nested objects and arrays) supported by this parser. + // + // Defaults to DEFAULT_MAX_DEPTH. + // + size_t _max_depth{0}; + + // all nodes are stored on the doc.tape using a 64-bit word. + // + // strings, double and ints are stored as + // a 64-bit word with a pointer to the actual value + // + // + // + // for objects or arrays, store [ or { at the beginning and } and ] at the + // end. For the openings ([ or {), we annotate them with a reference to the + // location on the doc.tape of the end, and for then closings (} and ]), we + // annotate them with a reference to the location of the opening + // + // + + really_inline void write_tape(uint64_t val, uint8_t c) { + doc.tape[current_loc++] = val | ((static_cast(c)) << 56); + } + + really_inline void annotate_previous_loc(uint32_t saved_loc, uint64_t val) { + doc.tape[saved_loc] |= val; + } + + // + // Set the current capacity: the largest document this parser can support without reallocating. + // + // This will allocate *or deallocate* as necessary. + // + // Returns false if allocation fails. + // + WARN_UNUSED bool set_capacity(size_t capacity); + + // + // Set the maximum level of nested object and arrays supported by this parser. + // + // This will allocate *or deallocate* as necessary. + // + // Returns false if allocation fails. + // + WARN_UNUSED bool set_max_depth(size_t max_depth); +}; // class parser } // namespace simdjson -#include "simdjson/document_parser.h" +#include "simdjson/inline/document.h" #include "simdjson/document_iterator.h" #endif // SIMDJSON_DOCUMENT_H \ No newline at end of file diff --git a/include/simdjson/document_iterator.h b/include/simdjson/document_iterator.h index 8a692f832..f5c77a21a 100644 --- a/include/simdjson/document_iterator.h +++ b/include/simdjson/document_iterator.h @@ -2,6 +2,7 @@ #define SIMDJSON_DOCUMENT_ITERATOR_H #include +#include #include #include #include @@ -258,487 +259,8 @@ public: scopeindex_t depth_index[max_depth]; }; -// Because of template weirdness, the actual class definition is inline in the document class - -template -WARN_UNUSED bool document_iterator::is_ok() const { - return location < tape_length; -} - -// useful for debuging purposes -template -size_t document_iterator::get_tape_location() const { - return location; -} - -// useful for debuging purposes -template -size_t document_iterator::get_tape_length() const { - return tape_length; -} - -// returns the current depth (start at 1 with 0 reserved for the fictitious root -// node) -template -size_t document_iterator::get_depth() const { - return depth; -} - -// A scope is a series of nodes at the same depth, typically it is either an -// object ({) or an array ([). The root node has type 'r'. -template -uint8_t document_iterator::get_scope_type() const { - return depth_index[depth].scope_type; -} - -template -bool document_iterator::move_forward() { - if (location + 1 >= tape_length) { - return false; // we are at the end! - } - - if ((current_type == '[') || (current_type == '{')) { - // We are entering a new scope - depth++; - assert(depth < max_depth); - depth_index[depth].start_of_scope = location; - depth_index[depth].scope_type = current_type; - } else if ((current_type == ']') || (current_type == '}')) { - // Leaving a scope. - depth--; - } else if (is_number()) { - // these types use 2 locations on the tape, not just one. - location += 1; - } - - location += 1; - current_val = doc.tape[location]; - current_type = (current_val >> 56); - return true; -} - -template -void document_iterator::move_to_value() { - // assume that we are on a key, so move by 1. - location += 1; - current_val = doc.tape[location]; - current_type = (current_val >> 56); -} - -template -bool document_iterator::move_to_key(const char *key) { - if (down()) { - do { - const bool right_key = (strcmp(get_string(), key) == 0); - move_to_value(); - if (right_key) { - return true; - } - } while (next()); - up(); - } - return false; -} - -template -bool document_iterator::move_to_key_insensitive( - const char *key) { - if (down()) { - do { - const bool right_key = (simdjson_strcasecmp(get_string(), key) == 0); - move_to_value(); - if (right_key) { - return true; - } - } while (next()); - up(); - } - return false; -} - -template -bool document_iterator::move_to_key(const char *key, - uint32_t length) { - if (down()) { - do { - bool right_key = ((get_string_length() == length) && - (memcmp(get_string(), key, length) == 0)); - move_to_value(); - if (right_key) { - return true; - } - } while (next()); - up(); - } - return false; -} - -template -bool document_iterator::move_to_index(uint32_t index) { - if (down()) { - uint32_t i = 0; - for (; i < index; i++) { - if (!next()) { - break; - } - } - if (i == index) { - return true; - } - up(); - } - return false; -} - -template bool document_iterator::prev() { - size_t target_location = location; - to_start_scope(); - size_t npos = location; - if (target_location == npos) { - return false; // we were already at the start - } - size_t oldnpos; - // we have that npos < target_location here - do { - oldnpos = npos; - if ((current_type == '[') || (current_type == '{')) { - // we need to jump - npos = (current_val & JSON_VALUE_MASK); - } else { - npos = npos + ((current_type == 'd' || current_type == 'l') ? 2 : 1); - } - } while (npos < target_location); - location = oldnpos; - current_val = doc.tape[location]; - current_type = current_val >> 56; - return true; -} - -template bool document_iterator::up() { - if (depth == 1) { - return false; // don't allow moving back to root - } - to_start_scope(); - // next we just move to the previous value - depth--; - location -= 1; - current_val = doc.tape[location]; - current_type = (current_val >> 56); - return true; -} - -template bool document_iterator::down() { - if (location + 1 >= tape_length) { - return false; - } - if ((current_type == '[') || (current_type == '{')) { - size_t npos = (current_val & JSON_VALUE_MASK); - if (npos == location + 2) { - return false; // we have an empty scope - } - depth++; - assert(depth < max_depth); - location = location + 1; - depth_index[depth].start_of_scope = location; - depth_index[depth].scope_type = current_type; - current_val = doc.tape[location]; - current_type = (current_val >> 56); - return true; - } - return false; -} - -template -void document_iterator::to_start_scope() { - location = depth_index[depth].start_of_scope; - current_val = doc.tape[location]; - current_type = (current_val >> 56); -} - -template bool document_iterator::next() { - size_t npos; - if ((current_type == '[') || (current_type == '{')) { - // we need to jump - npos = (current_val & JSON_VALUE_MASK); - } else { - npos = location + (is_number() ? 2 : 1); - } - uint64_t next_val = doc.tape[npos]; - uint8_t next_type = (next_val >> 56); - if ((next_type == ']') || (next_type == '}')) { - return false; // we reached the end of the scope - } - location = npos; - current_val = next_val; - current_type = next_type; - return true; -} - -template -document_iterator::document_iterator(const document &doc_) noexcept - : doc(doc_), depth(0), location(0), tape_length(0) { - depth_index[0].start_of_scope = location; - current_val = doc.tape[location++]; - current_type = (current_val >> 56); - depth_index[0].scope_type = current_type; - tape_length = current_val & JSON_VALUE_MASK; - if (location < tape_length) { - // If we make it here, then depth_capacity must >=2, but the compiler - // may not know this. - current_val = doc.tape[location]; - current_type = (current_val >> 56); - depth++; - assert(depth < max_depth); - depth_index[depth].start_of_scope = location; - depth_index[depth].scope_type = current_type; - } -} - -template -document_iterator::document_iterator(const document::parser &parser) - : document_iterator(parser.get_document()) {} - -template -document_iterator::document_iterator( - const document_iterator &o) noexcept - : doc(o.doc), depth(o.depth), location(o.location), - tape_length(o.tape_length), current_type(o.current_type), - current_val(o.current_val) { - memcpy(depth_index, o.depth_index, (depth + 1) * sizeof(depth_index[0])); -} - -template -document_iterator &document_iterator:: -operator=(const document_iterator &o) noexcept { - doc = o.doc; - depth = o.depth; - location = o.location; - tape_length = o.tape_length; - current_type = o.current_type; - current_val = o.current_val; - memcpy(depth_index, o.depth_index, (depth + 1) * sizeof(depth_index[0])); - return *this; -} - -template -bool document_iterator::print(std::ostream &os, bool escape_strings) const { - if (!is_ok()) { - return false; - } - switch (current_type) { - case '"': // we have a string - os << '"'; - if (escape_strings) { - print_with_escapes(get_string(), os, get_string_length()); - } else { - // was: os << get_string();, but given that we can include null chars, we - // have to do something crazier: - std::copy(get_string(), get_string() + get_string_length(), std::ostream_iterator(os)); - } - os << '"'; - break; - case 'l': // we have a long int - os << get_integer(); - break; - case 'u': - os << get_unsigned_integer(); - break; - case 'd': - os << get_double(); - 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 - case '}': // we end an object - case '[': // we start an array - case ']': // we end an array - os << static_cast(current_type); - break; - default: - return false; - } - return true; -} - -template -bool document_iterator::move_to(const char *pointer, - uint32_t length) { - char *new_pointer = nullptr; - if (pointer[0] == '#') { - // Converting fragment representation to string representation - new_pointer = new char[length]; - uint32_t new_length = 0; - for (uint32_t i = 1; i < length; i++) { - if (pointer[i] == '%' && pointer[i + 1] == 'x') { - try { - int fragment = - std::stoi(std::string(&pointer[i + 2], 2), nullptr, 16); - if (fragment == '\\' || fragment == '"' || (fragment <= 0x1F)) { - // escaping the character - new_pointer[new_length] = '\\'; - new_length++; - } - new_pointer[new_length] = fragment; - i += 3; - } catch (std::invalid_argument &) { - delete[] new_pointer; - return false; // the fragment is invalid - } - } else { - new_pointer[new_length] = pointer[i]; - } - new_length++; - } - length = new_length; - pointer = new_pointer; - } - - // saving the current state - size_t depth_s = depth; - size_t location_s = location; - uint8_t current_type_s = current_type; - uint64_t current_val_s = current_val; - - rewind(); // The json pointer is used from the root of the document. - - bool found = relative_move_to(pointer, length); - delete[] new_pointer; - - if (!found) { - // since the pointer has found nothing, we get back to the original - // position. - depth = depth_s; - location = location_s; - current_type = current_type_s; - current_val = current_val_s; - } - - return found; -} - -template -bool document_iterator::relative_move_to(const char *pointer, - uint32_t length) { - if (length == 0) { - // returns the whole document - return true; - } - - if (pointer[0] != '/') { - // '/' must be the first character - return false; - } - - // finding the key in an object or the index in an array - std::string key_or_index; - uint32_t offset = 1; - - // checking for the "-" case - if (is_array() && pointer[1] == '-') { - if (length != 2) { - // the pointer must be exactly "/-" - // there can't be anything more after '-' as an index - return false; - } - key_or_index = '-'; - offset = length; // will skip the loop coming right after - } - - // We either transform the first reference token to a valid json key - // or we make sure it is a valid index in an array. - for (; offset < length; offset++) { - if (pointer[offset] == '/') { - // beginning of the next key or index - break; - } - if (is_array() && (pointer[offset] < '0' || pointer[offset] > '9')) { - // the index of an array must be an integer - // we also make sure std::stoi won't discard whitespaces later - return false; - } - if (pointer[offset] == '~') { - // "~1" represents "/" - if (pointer[offset + 1] == '1') { - key_or_index += '/'; - offset++; - continue; - } - // "~0" represents "~" - if (pointer[offset + 1] == '0') { - key_or_index += '~'; - offset++; - continue; - } - } - if (pointer[offset] == '\\') { - if (pointer[offset + 1] == '\\' || pointer[offset + 1] == '"' || - (pointer[offset + 1] <= 0x1F)) { - key_or_index += pointer[offset + 1]; - offset++; - continue; - } - return false; // invalid escaped character - } - if (pointer[offset] == '\"') { - // unescaped quote character. this is an invalid case. - // lets do nothing and assume most pointers will be valid. - // it won't find any corresponding json key anyway. - // return false; - } - key_or_index += pointer[offset]; - } - - bool found = false; - if (is_object()) { - if (move_to_key(key_or_index.c_str(), key_or_index.length())) { - found = relative_move_to(pointer + offset, length - offset); - } - } else if (is_array()) { - if (key_or_index == "-") { // handling "-" case first - if (down()) { - while (next()) - ; // moving to the end of the array - // moving to the nonexistent value right after... - size_t npos; - if ((current_type == '[') || (current_type == '{')) { - // we need to jump - npos = (current_val & JSON_VALUE_MASK); - } else { - npos = - location + ((current_type == 'd' || current_type == 'l') ? 2 : 1); - } - location = npos; - current_val = doc.tape[npos]; - current_type = (current_val >> 56); - return true; // how could it fail ? - } - } else { // regular numeric index - // The index can't have a leading '0' - if (key_or_index[0] == '0' && key_or_index.length() > 1) { - return false; - } - // it cannot be empty - if (key_or_index.length() == 0) { - return false; - } - // we already checked the index contains only valid digits - uint32_t index = std::stoi(key_or_index); - if (move_to_index(index)) { - found = relative_move_to(pointer + offset, length - offset); - } - } - } - - return found; -} - } // namespace simdjson +#include "simdjson/inline/document_iterator.h" + #endif // SIMDJSON_DOCUMENT_ITERATOR_H diff --git a/include/simdjson/document_parser.h b/include/simdjson/document_parser.h deleted file mode 100644 index c59a3f57f..000000000 --- a/include/simdjson/document_parser.h +++ /dev/null @@ -1,597 +0,0 @@ -#ifndef SIMDJSON_DOCUMENT_PARSER_H -#define SIMDJSON_DOCUMENT_PARSER_H - -#include -#include -#include "simdjson/common_defs.h" -#include "simdjson/simdjson.h" -#include "simdjson/document.h" -#include "simdjson/padded_string.h" - -namespace simdjson { - -class document::parser { -public: - // - // Create a JSON parser with zero capacity. Call allocate_capacity() to initialize it. - // - parser()=default; - ~parser()=default; - - // this is a move only class - parser(document::parser &&p) = default; - parser(const document::parser &p) = delete; - parser &operator=(document::parser &&o) = default; - parser &operator=(const document::parser &o) = delete; - - // - // Parse a JSON document and return a reference to it. - // - // The JSON document still lives in the parser: this is the most efficient way to parse JSON - // documents because it reuses the same buffers, but you *must* use the document before you - // destroy the parser or call parse() again. - // - // Throws invalid_json if the JSON is invalid. - // - inline doc_ref_result parse(const uint8_t *buf, size_t len, bool realloc_if_needed = true); - inline doc_ref_result parse(const char *buf, size_t len, bool realloc_if_needed = true); - inline doc_ref_result parse(const std::string &s, bool realloc_if_needed = true); - inline doc_ref_result parse(const padded_string &s); - - // - // Current capacity: the largest document this parser can support without reallocating. - // - size_t capacity() { - return _capacity; - } - - // - // The maximum level of nested object and arrays supported by this parser. - // - size_t max_depth() { - return _max_depth; - } - - // if needed, allocate memory so that the object is able to process JSON - // documents having up to capacity bytes and max_depth "depth" - WARN_UNUSED bool allocate_capacity(size_t capacity, size_t max_depth = DEFAULT_MAX_DEPTH) { - return set_capacity(capacity) && set_max_depth(max_depth); - } - - // type aliases for backcompat - using Iterator = document::iterator; - using InvalidJSON = invalid_json; - class doc_result; - - // Next location to write to in the tape - uint32_t current_loc{0}; - - // structural indices passed from stage 1 to stage 2 - uint32_t n_structural_indexes{0}; - std::unique_ptr structural_indexes; - - // location and return address of each open { or [ - std::unique_ptr containing_scope_offset; -#ifdef SIMDJSON_USE_COMPUTED_GOTO - std::unique_ptr ret_address; -#else - std::unique_ptr ret_address; -#endif - - // Next place to write a string - uint8_t *current_string_buf_loc; - - bool valid{false}; - error_code error{simdjson::UNINITIALIZED}; - - // Document we're writing to - document doc; - - // returns true if the document parsed was valid - bool is_valid() const; - - // return an error code corresponding to the last parsing attempt, see - // simdjson.h will return simdjson::UNITIALIZED if no parsing was attempted - int get_error_code() const; - - // return the string equivalent of "get_error_code" - std::string get_error_message() const; - - // - // for backcompat with ParsedJson - // - - // print the json to std::ostream (should be valid) - // return false if the tape is likely wrong (e.g., you did not parse a valid - // JSON). - WARN_UNUSED - bool print_json(std::ostream &os) const; - WARN_UNUSED - bool dump_raw_tape(std::ostream &os) const; - - // this should be called when parsing (right before writing the tapes) - void init_stage2(); - - really_inline error_code on_error(error_code new_error_code) { - error = new_error_code; - return new_error_code; - } - really_inline error_code on_success(error_code success_code) { - error = success_code; - valid = true; - return success_code; - } - really_inline bool on_start_document(uint32_t depth) { - containing_scope_offset[depth] = current_loc; - write_tape(0, 'r'); - return true; - } - really_inline bool on_start_object(uint32_t depth) { - containing_scope_offset[depth] = current_loc; - write_tape(0, '{'); - return true; - } - really_inline bool on_start_array(uint32_t depth) { - containing_scope_offset[depth] = current_loc; - write_tape(0, '['); - return true; - } - // TODO we're not checking this bool - really_inline bool on_end_document(uint32_t depth) { - // write our doc.tape location to the header scope - // The root scope gets written *at* the previous location. - annotate_previous_loc(containing_scope_offset[depth], current_loc); - write_tape(containing_scope_offset[depth], 'r'); - return true; - } - really_inline bool on_end_object(uint32_t depth) { - // write our doc.tape location to the header scope - write_tape(containing_scope_offset[depth], '}'); - annotate_previous_loc(containing_scope_offset[depth], current_loc); - return true; - } - really_inline bool on_end_array(uint32_t depth) { - // write our doc.tape location to the header scope - write_tape(containing_scope_offset[depth], ']'); - annotate_previous_loc(containing_scope_offset[depth], current_loc); - return true; - } - - really_inline bool on_true_atom() { - write_tape(0, 't'); - return true; - } - really_inline bool on_false_atom() { - write_tape(0, 'f'); - return true; - } - really_inline bool on_null_atom() { - write_tape(0, 'n'); - return true; - } - - really_inline uint8_t *on_start_string() { - /* we advance the point, accounting for the fact that we have a NULL - * termination */ - write_tape(current_string_buf_loc - doc.string_buf.get(), '"'); - return current_string_buf_loc + sizeof(uint32_t); - } - - really_inline bool on_end_string(uint8_t *dst) { - uint32_t str_length = dst - (current_string_buf_loc + sizeof(uint32_t)); - // TODO 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. - memcpy(current_string_buf_loc, &str_length, sizeof(uint32_t)); - // NULL termination is still handy if you expect all your strings to - // be NULL terminated? It comes at a small cost - *dst = 0; - current_string_buf_loc = dst + 1; - return true; - } - - really_inline bool on_number_s64(int64_t value) { - write_tape(0, 'l'); - std::memcpy(&doc.tape[current_loc], &value, sizeof(value)); - ++current_loc; - return true; - } - really_inline bool on_number_u64(uint64_t value) { - write_tape(0, 'u'); - doc.tape[current_loc++] = value; - return true; - } - really_inline bool on_number_double(double value) { - write_tape(0, 'd'); - static_assert(sizeof(value) == sizeof(doc.tape[current_loc]), "mismatch size"); - memcpy(&doc.tape[current_loc++], &value, sizeof(double)); - // doc.tape[doc.current_loc++] = *((uint64_t *)&d); - return true; - } - - // - // Called before a parse is initiated. - // - // - Returns CAPACITY if the document is too large - // - Returns MEMALLOC if we needed to allocate memory and could not - // - WARN_UNUSED error_code init_parse(size_t len); - - const document &get_document() const { - if (!is_valid()) { - throw invalid_json(error); - } - return doc; - } - -private: - // - // The maximum document length this parser supports. - // - // Buffers are large enough to handle any document up to this length. - // - size_t _capacity{0}; - - // - // The maximum depth (number of nested objects and arrays) supported by this parser. - // - // Defaults to DEFAULT_MAX_DEPTH. - // - size_t _max_depth{0}; - - // all nodes are stored on the doc.tape using a 64-bit word. - // - // strings, double and ints are stored as - // a 64-bit word with a pointer to the actual value - // - // - // - // for objects or arrays, store [ or { at the beginning and } and ] at the - // end. For the openings ([ or {), we annotate them with a reference to the - // location on the doc.tape of the end, and for then closings (} and ]), we - // annotate them with a reference to the location of the opening - // - // - - // this should be considered a private function - really_inline void write_tape(uint64_t val, uint8_t c) { - doc.tape[current_loc++] = val | ((static_cast(c)) << 56); - } - - really_inline void annotate_previous_loc(uint32_t saved_loc, uint64_t val) { - doc.tape[saved_loc] |= val; - } - - // - // Set the current capacity: the largest document this parser can support without reallocating. - // - // This will allocate *or deallocate* as necessary. - // - // Returns false if allocation fails. - // - WARN_UNUSED bool set_capacity(size_t capacity); - - // - // Set the maximum level of nested object and arrays supported by this parser. - // - // This will allocate *or deallocate* as necessary. - // - // Returns false if allocation fails. - // - WARN_UNUSED bool set_max_depth(size_t max_depth); -}; - -// -// C API (json_parse and build_parsed_json) declarations -// - -// Parse a document found in buf. -// -// The content should be a valid JSON document encoded as UTF-8. If there is a -// UTF-8 BOM, the caller is responsible for omitting it, UTF-8 BOM are -// discouraged. -// -// You need to preallocate document::parser with a capacity of len (e.g., -// parser.allocate_capacity(len)). -// -// The function returns simdjson::SUCCESS (an integer = 0) in case of a success -// or an error code from simdjson/simdjson.h in case of failure such as -// simdjson::CAPACITY, simdjson::MEMALLOC, simdjson::DEPTH_ERROR and so forth; -// the simdjson::error_message function converts these error codes into a -// string). -// -// You can also check validity by calling parser.is_valid(). The same document::parser can -// be reused for other documents. -// -// If realloc_if_needed is true (default) then a temporary buffer is created -// when needed during processing (a copy of the input string is made). The input -// buf should be readable up to buf + len + SIMDJSON_PADDING if -// realloc_if_needed is false, all bytes at and after buf + len are ignored -// (can be garbage). The document::parser object can be reused. - -int json_parse(const uint8_t *buf, size_t len, document::parser &parser, - bool realloc_if_needed = true); - -// Parse a document found in buf. -// -// The content should be a valid JSON document encoded as UTF-8. If there is a -// UTF-8 BOM, the caller is responsible for omitting it, UTF-8 BOM are -// discouraged. -// -// You need to preallocate document::parser with a capacity of len (e.g., -// parser.allocate_capacity(len)). -// -// The function returns simdjson::SUCCESS (an integer = 0) in case of a success -// or an error code from simdjson/simdjson.h in case of failure such as -// simdjson::CAPACITY, simdjson::MEMALLOC, simdjson::DEPTH_ERROR and so forth; -// the simdjson::error_message function converts these error codes into a -// string). -// -// You can also check validity -// by calling parser.is_valid(). The same document::parser can be reused for other -// documents. -// -// If realloc_if_needed is true (default) then a temporary buffer is created -// when needed during processing (a copy of the input string is made). The input -// buf should be readable up to buf + len + SIMDJSON_PADDING if -// realloc_if_needed is false, all bytes at and after buf + len are ignored -// (can be garbage). The document::parser object can be reused. -int json_parse(const char *buf, size_t len, document::parser &parser, - bool realloc_if_needed = true); - -// We do not want to allow implicit conversion from C string to std::string. -int json_parse(const char *buf, document::parser &parser) = delete; - -// Parse a document found in in string s. -// You need to preallocate document::parser with a capacity of len (e.g., -// parser.allocate_capacity(len)). -// -// The function returns simdjson::SUCCESS (an integer = 0) in case of a success -// or an error code from simdjson/simdjson.h in case of failure such as -// simdjson::CAPACITY, simdjson::MEMALLOC, simdjson::DEPTH_ERROR and so forth; -// the simdjson::error_message function converts these error codes into a -// string). -// -// A temporary buffer is created when needed during processing -// (a copy of the input string is made). -inline int json_parse(const std::string &s, document::parser &parser) { - return json_parse(s.data(), s.length(), parser, true); -} - -// Parse a document found in in string s. -// -// The content should be a valid JSON document encoded as UTF-8. If there is a -// UTF-8 BOM, the caller is responsible for omitting it, UTF-8 BOM are -// discouraged. -// -// You need to preallocate document::parser with a capacity of len (e.g., -// parser.allocate_capacity(len)). -// -// The function returns simdjson::SUCCESS (an integer = 0) in case of a success -// or an error code from simdjson/simdjson.h in case of failure such as -// simdjson::CAPACITY, simdjson::MEMALLOC, simdjson::DEPTH_ERROR and so forth; -// the simdjson::error_message function converts these error codes into a -// string). -// -// You can also check validity -// by calling parser.is_valid(). The same document::parser can be reused for other -// documents. -inline int json_parse(const padded_string &s, document::parser &parser) { - return json_parse(s.data(), s.length(), parser, false); -} - -// Build a document::parser object. You can check validity -// by calling parser.is_valid(). This does the memory allocation needed for -// document::parser. If realloc_if_needed is true (default) then a temporary buffer is -// created when needed during processing (a copy of the input string is made). -// -// The input buf should be readable up to buf + len + SIMDJSON_PADDING if -// realloc_if_needed is false, all bytes at and after buf + len are ignored -// (can be garbage). -// -// The content should be a valid JSON document encoded as UTF-8. If there is a -// UTF-8 BOM, the caller is responsible for omitting it, UTF-8 BOM are -// discouraged. -// -// This is a convenience function which calls json_parse. -WARN_UNUSED -document::parser build_parsed_json(const uint8_t *buf, size_t len, - bool realloc_if_needed = true); - -WARN_UNUSED -// Build a document::parser object. You can check validity -// by calling parser.is_valid(). This does the memory allocation needed for -// document::parser. If realloc_if_needed is true (default) then a temporary buffer is -// created when needed during processing (a copy of the input string is made). -// -// The input buf should be readable up to buf + len + SIMDJSON_PADDING if -// realloc_if_needed is false, all bytes at and after buf + len are ignored -// (can be garbage). -// -// -// The content should be a valid JSON document encoded as UTF-8. If there is a -// UTF-8 BOM, the caller is responsible for omitting it, UTF-8 BOM are -// discouraged. -// -// This is a convenience function which calls json_parse. -inline document::parser build_parsed_json(const char *buf, size_t len, - bool realloc_if_needed = true) { - return build_parsed_json(reinterpret_cast(buf), len, - realloc_if_needed); -} - -// We do not want to allow implicit conversion from C string to std::string. -document::parser build_parsed_json(const char *buf) = delete; - -// Parse a document found in in string s. -// You need to preallocate document::parser with a capacity of len (e.g., -// parser.allocate_capacity(len)). Return SUCCESS (an integer = 0) in case of a -// success. You can also check validity by calling parser.is_valid(). The same -// document::parser can be reused for other documents. -// -// A temporary buffer is created when needed during processing -// (a copy of the input string is made). -// -// The content should be a valid JSON document encoded as UTF-8. If there is a -// UTF-8 BOM, the caller is responsible for omitting it, UTF-8 BOM are -// discouraged. -// -// This is a convenience function which calls json_parse. -WARN_UNUSED -inline document::parser build_parsed_json(const std::string &s) { - return build_parsed_json(s.data(), s.length(), true); -} - -// Parse a document found in in string s. -// You need to preallocate document::parser with a capacity of len (e.g., -// parser.allocate_capacity(len)). Return SUCCESS (an integer = 0) in case of a -// success. You can also check validity by calling parser.is_valid(). The same -// document::parser can be reused for other documents. -// -// The content should be a valid JSON document encoded as UTF-8. If there is a -// UTF-8 BOM, the caller is responsible for omitting it, UTF-8 BOM are -// discouraged. -// -// This is a convenience function which calls json_parse. -WARN_UNUSED -inline document::parser build_parsed_json(const padded_string &s) { - return build_parsed_json(s.data(), s.length(), false); -} - - -// -// Stage 1 implementation declarations -// - -// Setting the streaming parameter to true allows the find_structural_bits to tolerate unclosed strings. -// The caller should still ensure that the input is valid UTF-8. If you are processing substrings, -// you may want to call on a function like trimmed_length_safe_utf8. -// A function like find_last_json_buf_idx may also prove useful. -template -int find_structural_bits(const uint8_t *buf, size_t len, document::parser &parser, bool streaming); - -// Setting the streaming parameter to true allows the find_structural_bits to tolerate unclosed strings. -// The caller should still ensure that the input is valid UTF-8. If you are processing substrings, -// you may want to call on a function like trimmed_length_safe_utf8. -// A function like find_last_json_buf_idx may also prove useful. -template -int find_structural_bits(const char *buf, size_t len, document::parser &parser, bool streaming) { - return find_structural_bits((const uint8_t *)buf, len, parser, streaming); -} - -template -int find_structural_bits(const uint8_t *buf, size_t len, document::parser &parser) { - return find_structural_bits(buf, len, parser, false); -} - -template -int find_structural_bits(const char *buf, size_t len, document::parser &parser) { - return find_structural_bits((const uint8_t *)buf, len, parser); -} - -// -// Stage 2 implementation declarations -// - -template -WARN_UNUSED int -unified_machine(const uint8_t *buf, size_t len, document::parser &parser); - -template -WARN_UNUSED int -unified_machine(const char *buf, size_t len, document::parser &parser) { - return unified_machine(reinterpret_cast(buf), len, parser); -} - - -// Streaming -template -WARN_UNUSED int -unified_machine(const uint8_t *buf, size_t len, document::parser &parser, size_t &next_json); - -template -int unified_machine(const char *buf, size_t len, document::parser &parser, size_t &next_json) { - return unified_machine(reinterpret_cast(buf), len, parser, next_json); -} - -} // namespace simdjson - -// -// Inline implementation -// - -#include "simdjson/document_parser.h" -#include "simdjson/stage1_find_marks.h" -#include "simdjson/stage2_build_tape.h" - -namespace simdjson { - -inline document::doc_ref_result document::parser::parse(const uint8_t *buf, size_t len, bool realloc_if_needed) { - auto code = (error_code)json_parse(buf, len, *this, realloc_if_needed); - valid = false; - error = UNINITIALIZED; - return document::doc_ref_result(doc, code); -} -really_inline document::doc_ref_result document::parser::parse(const char *buf, size_t len, bool realloc_if_needed) { - return parse((const uint8_t *)buf, len, realloc_if_needed); -} -really_inline document::doc_ref_result document::parser::parse(const std::string &s, bool realloc_if_needed) { - return parse(s.data(), s.length(), realloc_if_needed); -} -really_inline document::doc_ref_result document::parser::parse(const padded_string &s) { - return parse(s.data(), s.length(), false); -} - -inline document::doc_result document::parse(const uint8_t *buf, size_t len, bool realloc_if_needed) { - document::parser parser; - if (!parser.allocate_capacity(len)) { - return MEMALLOC; - } - auto [doc, error] = parser.parse(buf, len, realloc_if_needed); - return document::doc_result((document &&)doc, error); -} -inline document::doc_result document::parse(const char *buf, size_t len, bool realloc_if_needed) { - return parse((const uint8_t *)buf, len, realloc_if_needed); -} -inline document::doc_result document::parse(const std::string &s, bool realloc_if_needed) { - return parse(s.data(), s.length(), realloc_if_needed); -} -inline document::doc_result document::parse(const padded_string &s) { - return parse(s.data(), s.length(), false); -} - -// json_parse_implementation is the generic function, it is specialized for -// various architectures, e.g., as -// json_parse_implementation or -// json_parse_implementation -template -int json_parse_implementation(const uint8_t *buf, size_t len, document::parser &parser, - bool realloc_if_needed = true) { - int result = parser.init_parse(len); - if (result != SUCCESS) { return result; } - bool reallocated = false; - if (realloc_if_needed) { - const uint8_t *tmp_buf = buf; - buf = (uint8_t *)allocate_padded_buffer(len); - if (buf == NULL) - return simdjson::MEMALLOC; - memcpy((void *)buf, tmp_buf, len); - reallocated = true; - } - int stage1_err = simdjson::find_structural_bits(buf, len, parser); - if (stage1_err != simdjson::SUCCESS) { - if (reallocated) { // must free before we exit - aligned_free((void *)buf); - } - return stage1_err; - } - int res = unified_machine(buf, len, parser); - if (reallocated) { - aligned_free((void *)buf); - } - return res; -} - -} // namespace simdjson - -#endif // SIMDJSON_DOCUMENT_PARSER_H \ No newline at end of file diff --git a/include/simdjson/error.h b/include/simdjson/error.h index 6503db50a..8f0d91ea7 100644 --- a/include/simdjson/error.h +++ b/include/simdjson/error.h @@ -23,10 +23,11 @@ enum error_code { EMPTY, // no structural element found UNESCAPED_CHARS, // found unescaped characters in a string. UNCLOSED_STRING, // missing quote at the end + UNSUPPORTED_ARCHITECTURE, // unsupported architecture UNEXPECTED_ERROR // indicative of a bug in simdjson }; -const std::string &error_message(error_code error); +const std::string &error_message(error_code error) noexcept; struct invalid_json : public std::exception { invalid_json(error_code _error) : error{_error} {} @@ -34,9 +35,9 @@ struct invalid_json : public std::exception { error_code error; }; -// backcompat +// TODO these are deprecated, remove using ErrorValues = error_code; -inline const std::string &error_message(int error) { return error_message(error_code(error)); } +inline const std::string &error_message(int error) noexcept { return error_message(error_code(error)); } } // namespace simdjson diff --git a/include/simdjson/implementation.h b/include/simdjson/implementation.h new file mode 100644 index 000000000..2bf8a013f --- /dev/null +++ b/include/simdjson/implementation.h @@ -0,0 +1,238 @@ +// Declaration order requires we get to document.h before implementation.h no matter what +#include "simdjson/document.h" + +#ifndef SIMDJSON_IMPLEMENTATION_H +#define SIMDJSON_IMPLEMENTATION_H + +#include +#include +#include +#include + +namespace simdjson { + +/** + * An implementation of simdjson for a particular CPU architecture. + * + * Also used to maintain the currently active implementation. The active implementation is + * automatically initialized on first use to the most advanced implementation supported by the host. + */ +class implementation { +public: + /** + * The name of this implementation. + * + * const implementation *impl = simdjson::active_implementation; + * cout << "simdjson is optimized for " << impl->name() << "(" << impl->description() << ")" << endl; + * + * @return the name of the implementation, e.g. "haswell", "westmere", "arm64" + */ + virtual const std::string &name() const { return _name; } + + /** + * The description of this implementation. + * + * const implementation *impl = simdjson::active_implementation; + * cout << "simdjson is optimized for " << impl->name() << "(" << impl->description() << ")" << endl; + * + * @return the name of the implementation, e.g. "haswell", "westmere", "arm64" + */ + virtual const std::string &description() const { return _description; } + + /** + * The instruction sets this implementation is compiled against. + * + * @return a mask of all required `instruction_set` values + */ + virtual uint32_t required_instruction_sets() const { return _required_instruction_sets; }; + + /** + * Run a full document parse (init_parse, stage1 and stage2). + * + * Overridden by each implementation. + * + * @param buf the json document to parse. *MUST* be allocated up to len + SIMDJSON_PADDING bytes. + * @param len the length of the json document. + * @param parser the parser with the buffers to use. *MUST* have allocated up to at least len capacity. + * @return the error code, or SUCCESS if there was no error. + */ + WARN_UNUSED virtual error_code parse(const uint8_t *buf, size_t len, document::parser &parser) const noexcept = 0; + + /** + * Stage 1 of the document parser. + * + * Overridden by each implementation. + * + * @param buf the json document to parse. *MUST* be allocated up to len + SIMDJSON_PADDING bytes. + * @param len the length of the json document. + * @param parser the parser with the buffers to use. *MUST* have allocated up to at least len capacity. + * @param streaming whether this is being called by a JsonStream parser. + * @return the error code, or SUCCESS if there was no error. + */ + WARN_UNUSED virtual error_code stage1(const uint8_t *buf, size_t len, document::parser &parser, bool streaming) const noexcept = 0; + + /** + * Stage 2 of the document parser. + * + * Overridden by each implementation. + * + * @param buf the json document to parse. *MUST* be allocated up to len + SIMDJSON_PADDING bytes. + * @param len the length of the json document. + * @param parser the parser with the buffers to use. *MUST* have allocated up to at least len capacity. + * @return the error code, or SUCCESS if there was no error. + */ + WARN_UNUSED virtual error_code stage2(const uint8_t *buf, size_t len, document::parser &parser) const noexcept = 0; + + /** + * Stage 2 of the document parser for JsonStream. + * + * Overridden by each implementation. + * + * @param buf the json document to parse. *MUST* be allocated up to len + SIMDJSON_PADDING bytes. + * @param len the length of the json document. + * @param parser the parser with the buffers to use. *MUST* have allocated up to at least len capacity. + * @param next_json the next structural index. Start this at 0 the first time, and it will be updated to the next value to pass each time. + * @return the error code, SUCCESS if there was no error, or SUCCESS_AND_HAS_MORE if there was no error and stage2 can be called again. + */ + WARN_UNUSED virtual error_code stage2(const uint8_t *buf, size_t len, document::parser &parser, size_t &next_json) const noexcept = 0; + +protected: + really_inline implementation( + const std::string &name, + const std::string &description, + uint32_t required_instruction_sets + ) : + _name(name), + _description(description), + _required_instruction_sets(required_instruction_sets) + { + } + +private: + /** + * The name of this implementation. + */ + const std::string _name; + + /** + * The description of this implementation. + */ + const std::string _description; + + /** + * Instruction sets required for this implementation. + */ + const uint32_t _required_instruction_sets; +}; + +namespace internal { + +/** + * The list of available implementations compiled into simdjson. + */ +class available_implementation_list { +public: + /** Get the list of available implementations compiled into simdjson */ + really_inline available_implementation_list() {} + /** Number of implementations */ + size_t size() const noexcept; + /** STL const begin() iterator */ + const implementation * const *begin() const noexcept; + /** STL const end() iterator */ + const implementation * const *end() const noexcept; + + /** + * Get the implementation with the given name. + * + * Case sensitive. + * + * const implementation *impl = simdjson::available_implementations["westmere"]; + * if (!impl) { exit(1); } + * simdjson::active_implementation = impl; + * + * @param name the implementation to find, e.g. "westmere", "haswell", "arm64" + * @return the implementation, or nullptr if the parse failed. + */ + const implementation * operator[](const std::string& name) const noexcept { + for (const implementation * impl : *this) { + if (impl->name() == name) { return impl; } + } + return nullptr; + } + + /** + * Detect the most advanced implementation supported by the current host. + * + * This is used to initialize the implementation on startup. + * + * const implementation *impl = simdjson::available_implementation::detect_best_supported(); + * simdjson::active_implementation = impl; + * + * @return the most advanced supported implementation for the current host, or an + * implementation that returns UNSUPPORTED_ARCHITECTURE if there is no supported + * implementation. Will never return nullptr. + */ + const implementation *detect_best_supported() const noexcept; +}; + +// Detects best supported implementation on first use, and sets it +class detect_best_supported_implementation_on_first_use final : public implementation { +public: + const std::string& name() const noexcept final { return set_best()->name(); } + const std::string& description() const noexcept final { return set_best()->description(); } + uint32_t required_instruction_sets() const noexcept final { return set_best()->required_instruction_sets(); } + WARN_UNUSED error_code parse(const uint8_t *buf, size_t len, document::parser &parser) const noexcept final { + return set_best()->parse(buf, len, parser); + } + WARN_UNUSED error_code stage1(const uint8_t *buf, size_t len, document::parser &parser, bool streaming) const noexcept final { + return set_best()->stage1(buf, len, parser, streaming); + } + WARN_UNUSED error_code stage2(const uint8_t *buf, size_t len, document::parser &parser) const noexcept final { + return set_best()->stage2(buf, len, parser); + } + WARN_UNUSED error_code stage2(const uint8_t *buf, size_t len, document::parser &parser, size_t &next_json) const noexcept final { + return set_best()->stage2(buf, len, parser, next_json); + } + + really_inline detect_best_supported_implementation_on_first_use() noexcept : implementation("best_supported_detector", "Detects the best supported implementation and sets it", 0) {} +private: + const implementation *set_best() const noexcept; +}; + +inline const detect_best_supported_implementation_on_first_use detect_best_supported_implementation_on_first_use_singleton; + +template +class atomic_ptr { +public: + atomic_ptr(T *_ptr) : ptr{_ptr} {} + + operator const T*() const { return ptr.load(); } + const T& operator*() const { return *ptr; } + const T* operator->() const { return ptr.load(); } + + operator T*() { return ptr.load(); } + T& operator*() { return *ptr; } + T* operator->() { return ptr.load(); } + T* operator=(T *_ptr) { return ptr = _ptr; } + +private: + std::atomic ptr; +}; + +} // namespace [simdjson::]internal + +/** + * The list of available implementations compiled into simdjson. + */ +inline const internal::available_implementation_list available_implementations; + +/** + * The active implementation. + * + * Automatically initialized on first use to the most advanced implementation supported by this hardware. + */ +inline internal::atomic_ptr active_implementation = &internal::detect_best_supported_implementation_on_first_use_singleton; + +} // namespace simdjson + +#endif // SIMDJSON_IMPLEMENTATION_H diff --git a/include/simdjson/inline/document.h b/include/simdjson/inline/document.h new file mode 100644 index 000000000..a96d36d66 --- /dev/null +++ b/include/simdjson/inline/document.h @@ -0,0 +1,192 @@ +#ifndef SIMDJSON_INLINE_DOCUMENT_H +#define SIMDJSON_INLINE_DOCUMENT_H + +#ifndef SIMDJSON_DOCUMENT_H +#error This is an internal file only. Include document.h instead. +#endif + +// Inline implementations go in here if they aren't small enough to go in the class itself or if +// there are complex header file dependencies that need to be broken by externalizing the +// implementation. + +#include "simdjson/implementation.h" + +namespace simdjson { + +// TODO inline? +document::doc_ref_result document::parser::parse(const uint8_t *buf, size_t len, bool realloc_if_needed) noexcept { + error_code code = init_parse(len); + if (code) { return document::doc_ref_result(doc, code); } + + if (realloc_if_needed) { + const uint8_t *tmp_buf = buf; + buf = (uint8_t *)allocate_padded_buffer(len); + if (buf == nullptr) + return document::doc_ref_result(doc, MEMALLOC); + memcpy((void *)buf, tmp_buf, len); + } + + code = simdjson::active_implementation->parse(buf, len, *this); + + // We're indicating validity via the doc_ref_result, so set the parse state back to invalid + valid = false; + error = UNINITIALIZED; + if (realloc_if_needed) { + aligned_free((void *)buf); // must free before we exit + } + return document::doc_ref_result(doc, code); +} +really_inline document::doc_ref_result document::parser::parse(const char *buf, size_t len, bool realloc_if_needed) noexcept { + return parse((const uint8_t *)buf, len, realloc_if_needed); +} +really_inline document::doc_ref_result document::parser::parse(const std::string &s, bool realloc_if_needed) noexcept { + return parse(s.data(), s.length(), realloc_if_needed); +} +really_inline document::doc_ref_result document::parser::parse(const padded_string &s) noexcept { + return parse(s.data(), s.length(), false); +} + +// TODO really_inline? +inline document::doc_result document::parse(const uint8_t *buf, size_t len, bool realloc_if_needed) noexcept { + document::parser parser; + if (!parser.allocate_capacity(len)) { + return MEMALLOC; + } + auto [doc, error] = parser.parse(buf, len, realloc_if_needed); + return document::doc_result((document &&)doc, error); +} +really_inline document::doc_result document::parse(const char *buf, size_t len, bool realloc_if_needed) noexcept { + return parse((const uint8_t *)buf, len, realloc_if_needed); +} +really_inline document::doc_result document::parse(const std::string &s, bool realloc_if_needed) noexcept { + return parse(s.data(), s.length(), realloc_if_needed); +} +really_inline document::doc_result document::parse(const padded_string &s) noexcept { + return parse(s.data(), s.length(), false); +} + +// +// Parser callbacks +// + +WARN_UNUSED +inline error_code document::parser::init_parse(size_t len) { + if (len > capacity()) { + return error = CAPACITY; + } + // If the last doc was taken, we need to allocate a new one + if (!doc.tape) { + if (!doc.set_capacity(len)) { + return error = MEMALLOC; + } + } + return SUCCESS; +} + +inline void document::parser::init_stage2() { + current_string_buf_loc = doc.string_buf.get(); + current_loc = 0; + valid = false; + error = UNINITIALIZED; +} + +really_inline error_code document::parser::on_error(error_code new_error_code) { + error = new_error_code; + return new_error_code; +} +really_inline error_code document::parser::on_success(error_code success_code) { + error = success_code; + valid = true; + return success_code; +} +really_inline bool document::parser::on_start_document(uint32_t depth) { + containing_scope_offset[depth] = current_loc; + write_tape(0, 'r'); + return true; +} +really_inline bool document::parser::on_start_object(uint32_t depth) { + containing_scope_offset[depth] = current_loc; + write_tape(0, '{'); + return true; +} +really_inline bool document::parser::on_start_array(uint32_t depth) { + containing_scope_offset[depth] = current_loc; + write_tape(0, '['); + return true; +} +// TODO we're not checking this bool +really_inline bool document::parser::on_end_document(uint32_t depth) { + // write our doc.tape location to the header scope + // The root scope gets written *at* the previous location. + annotate_previous_loc(containing_scope_offset[depth], current_loc); + write_tape(containing_scope_offset[depth], 'r'); + return true; +} +really_inline bool document::parser::on_end_object(uint32_t depth) { + // write our doc.tape location to the header scope + write_tape(containing_scope_offset[depth], '}'); + annotate_previous_loc(containing_scope_offset[depth], current_loc); + return true; +} +really_inline bool document::parser::on_end_array(uint32_t depth) { + // write our doc.tape location to the header scope + write_tape(containing_scope_offset[depth], ']'); + annotate_previous_loc(containing_scope_offset[depth], current_loc); + return true; +} + +really_inline bool document::parser::on_true_atom() { + write_tape(0, 't'); + return true; +} +really_inline bool document::parser::on_false_atom() { + write_tape(0, 'f'); + return true; +} +really_inline bool document::parser::on_null_atom() { + write_tape(0, 'n'); + return true; +} + +really_inline uint8_t *document::parser::on_start_string() { + /* we advance the point, accounting for the fact that we have a NULL + * termination */ + write_tape(current_string_buf_loc - doc.string_buf.get(), '"'); + return current_string_buf_loc + sizeof(uint32_t); +} + +really_inline bool document::parser::on_end_string(uint8_t *dst) { + uint32_t str_length = dst - (current_string_buf_loc + sizeof(uint32_t)); + // TODO 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. + memcpy(current_string_buf_loc, &str_length, sizeof(uint32_t)); + // NULL termination is still handy if you expect all your strings to + // be NULL terminated? It comes at a small cost + *dst = 0; + current_string_buf_loc = dst + 1; + return true; +} + +really_inline bool document::parser::on_number_s64(int64_t value) { + write_tape(0, 'l'); + std::memcpy(&doc.tape[current_loc], &value, sizeof(value)); + ++current_loc; + return true; +} +really_inline bool document::parser::on_number_u64(uint64_t value) { + write_tape(0, 'u'); + doc.tape[current_loc++] = value; + return true; +} +really_inline bool document::parser::on_number_double(double value) { + write_tape(0, 'd'); + static_assert(sizeof(value) == sizeof(doc.tape[current_loc]), "mismatch size"); + memcpy(&doc.tape[current_loc++], &value, sizeof(double)); + // doc.tape[doc.current_loc++] = *((uint64_t *)&d); + return true; +} + +} // namespace simdjson + +#endif // SIMDJSON_INLINE_DOCUMENT_H diff --git a/include/simdjson/inline/document_iterator.h b/include/simdjson/inline/document_iterator.h new file mode 100644 index 000000000..9cf63bed8 --- /dev/null +++ b/include/simdjson/inline/document_iterator.h @@ -0,0 +1,493 @@ +#ifndef SIMDJSON_INLINE_DOCUMENT_ITERATOR_H +#define SIMDJSON_INLINE_DOCUMENT_ITERATOR_H + +#ifndef SIMDJSON_DOCUMENT_ITERATOR_H +#error This is an internal file only. Include document.h instead. +#endif + +namespace simdjson { + +// Because of template weirdness, the actual class definition is inline in the document class + +template +WARN_UNUSED bool document_iterator::is_ok() const { + return location < tape_length; +} + +// useful for debuging purposes +template +size_t document_iterator::get_tape_location() const { + return location; +} + +// useful for debuging purposes +template +size_t document_iterator::get_tape_length() const { + return tape_length; +} + +// returns the current depth (start at 1 with 0 reserved for the fictitious root +// node) +template +size_t document_iterator::get_depth() const { + return depth; +} + +// A scope is a series of nodes at the same depth, typically it is either an +// object ({) or an array ([). The root node has type 'r'. +template +uint8_t document_iterator::get_scope_type() const { + return depth_index[depth].scope_type; +} + +template +bool document_iterator::move_forward() { + if (location + 1 >= tape_length) { + return false; // we are at the end! + } + + if ((current_type == '[') || (current_type == '{')) { + // We are entering a new scope + depth++; + assert(depth < max_depth); + depth_index[depth].start_of_scope = location; + depth_index[depth].scope_type = current_type; + } else if ((current_type == ']') || (current_type == '}')) { + // Leaving a scope. + depth--; + } else if (is_number()) { + // these types use 2 locations on the tape, not just one. + location += 1; + } + + location += 1; + current_val = doc.tape[location]; + current_type = (current_val >> 56); + return true; +} + +template +void document_iterator::move_to_value() { + // assume that we are on a key, so move by 1. + location += 1; + current_val = doc.tape[location]; + current_type = (current_val >> 56); +} + +template +bool document_iterator::move_to_key(const char *key) { + if (down()) { + do { + const bool right_key = (strcmp(get_string(), key) == 0); + move_to_value(); + if (right_key) { + return true; + } + } while (next()); + up(); + } + return false; +} + +template +bool document_iterator::move_to_key_insensitive( + const char *key) { + if (down()) { + do { + const bool right_key = (simdjson_strcasecmp(get_string(), key) == 0); + move_to_value(); + if (right_key) { + return true; + } + } while (next()); + up(); + } + return false; +} + +template +bool document_iterator::move_to_key(const char *key, + uint32_t length) { + if (down()) { + do { + bool right_key = ((get_string_length() == length) && + (memcmp(get_string(), key, length) == 0)); + move_to_value(); + if (right_key) { + return true; + } + } while (next()); + up(); + } + return false; +} + +template +bool document_iterator::move_to_index(uint32_t index) { + if (down()) { + uint32_t i = 0; + for (; i < index; i++) { + if (!next()) { + break; + } + } + if (i == index) { + return true; + } + up(); + } + return false; +} + +template bool document_iterator::prev() { + size_t target_location = location; + to_start_scope(); + size_t npos = location; + if (target_location == npos) { + return false; // we were already at the start + } + size_t oldnpos; + // we have that npos < target_location here + do { + oldnpos = npos; + if ((current_type == '[') || (current_type == '{')) { + // we need to jump + npos = (current_val & JSON_VALUE_MASK); + } else { + npos = npos + ((current_type == 'd' || current_type == 'l') ? 2 : 1); + } + } while (npos < target_location); + location = oldnpos; + current_val = doc.tape[location]; + current_type = current_val >> 56; + return true; +} + +template bool document_iterator::up() { + if (depth == 1) { + return false; // don't allow moving back to root + } + to_start_scope(); + // next we just move to the previous value + depth--; + location -= 1; + current_val = doc.tape[location]; + current_type = (current_val >> 56); + return true; +} + +template bool document_iterator::down() { + if (location + 1 >= tape_length) { + return false; + } + if ((current_type == '[') || (current_type == '{')) { + size_t npos = (current_val & JSON_VALUE_MASK); + if (npos == location + 2) { + return false; // we have an empty scope + } + depth++; + assert(depth < max_depth); + location = location + 1; + depth_index[depth].start_of_scope = location; + depth_index[depth].scope_type = current_type; + current_val = doc.tape[location]; + current_type = (current_val >> 56); + return true; + } + return false; +} + +template +void document_iterator::to_start_scope() { + location = depth_index[depth].start_of_scope; + current_val = doc.tape[location]; + current_type = (current_val >> 56); +} + +template bool document_iterator::next() { + size_t npos; + if ((current_type == '[') || (current_type == '{')) { + // we need to jump + npos = (current_val & JSON_VALUE_MASK); + } else { + npos = location + (is_number() ? 2 : 1); + } + uint64_t next_val = doc.tape[npos]; + uint8_t next_type = (next_val >> 56); + if ((next_type == ']') || (next_type == '}')) { + return false; // we reached the end of the scope + } + location = npos; + current_val = next_val; + current_type = next_type; + return true; +} + +template +document_iterator::document_iterator(const document &doc_) noexcept + : doc(doc_), depth(0), location(0), tape_length(0) { + depth_index[0].start_of_scope = location; + current_val = doc.tape[location++]; + current_type = (current_val >> 56); + depth_index[0].scope_type = current_type; + tape_length = current_val & JSON_VALUE_MASK; + if (location < tape_length) { + // If we make it here, then depth_capacity must >=2, but the compiler + // may not know this. + current_val = doc.tape[location]; + current_type = (current_val >> 56); + depth++; + assert(depth < max_depth); + depth_index[depth].start_of_scope = location; + depth_index[depth].scope_type = current_type; + } +} + +template +document_iterator::document_iterator(const document::parser &parser) + : document_iterator(parser.get_document()) {} + +template +document_iterator::document_iterator( + const document_iterator &o) noexcept + : doc(o.doc), depth(o.depth), location(o.location), + tape_length(o.tape_length), current_type(o.current_type), + current_val(o.current_val) { + memcpy(depth_index, o.depth_index, (depth + 1) * sizeof(depth_index[0])); +} + +template +document_iterator &document_iterator:: +operator=(const document_iterator &o) noexcept { + doc = o.doc; + depth = o.depth; + location = o.location; + tape_length = o.tape_length; + current_type = o.current_type; + current_val = o.current_val; + memcpy(depth_index, o.depth_index, (depth + 1) * sizeof(depth_index[0])); + return *this; +} + +template +bool document_iterator::print(std::ostream &os, bool escape_strings) const { + if (!is_ok()) { + return false; + } + switch (current_type) { + case '"': // we have a string + os << '"'; + if (escape_strings) { + print_with_escapes(get_string(), os, get_string_length()); + } else { + // was: os << get_string();, but given that we can include null chars, we + // have to do something crazier: + std::copy(get_string(), get_string() + get_string_length(), std::ostream_iterator(os)); + } + os << '"'; + break; + case 'l': // we have a long int + os << get_integer(); + break; + case 'u': + os << get_unsigned_integer(); + break; + case 'd': + os << get_double(); + 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 + case '}': // we end an object + case '[': // we start an array + case ']': // we end an array + os << static_cast(current_type); + break; + default: + return false; + } + return true; +} + +template +bool document_iterator::move_to(const char *pointer, + uint32_t length) { + char *new_pointer = nullptr; + if (pointer[0] == '#') { + // Converting fragment representation to string representation + new_pointer = new char[length]; + uint32_t new_length = 0; + for (uint32_t i = 1; i < length; i++) { + if (pointer[i] == '%' && pointer[i + 1] == 'x') { + try { + int fragment = + std::stoi(std::string(&pointer[i + 2], 2), nullptr, 16); + if (fragment == '\\' || fragment == '"' || (fragment <= 0x1F)) { + // escaping the character + new_pointer[new_length] = '\\'; + new_length++; + } + new_pointer[new_length] = fragment; + i += 3; + } catch (std::invalid_argument &) { + delete[] new_pointer; + return false; // the fragment is invalid + } + } else { + new_pointer[new_length] = pointer[i]; + } + new_length++; + } + length = new_length; + pointer = new_pointer; + } + + // saving the current state + size_t depth_s = depth; + size_t location_s = location; + uint8_t current_type_s = current_type; + uint64_t current_val_s = current_val; + + rewind(); // The json pointer is used from the root of the document. + + bool found = relative_move_to(pointer, length); + delete[] new_pointer; + + if (!found) { + // since the pointer has found nothing, we get back to the original + // position. + depth = depth_s; + location = location_s; + current_type = current_type_s; + current_val = current_val_s; + } + + return found; +} + +template +bool document_iterator::relative_move_to(const char *pointer, + uint32_t length) { + if (length == 0) { + // returns the whole document + return true; + } + + if (pointer[0] != '/') { + // '/' must be the first character + return false; + } + + // finding the key in an object or the index in an array + std::string key_or_index; + uint32_t offset = 1; + + // checking for the "-" case + if (is_array() && pointer[1] == '-') { + if (length != 2) { + // the pointer must be exactly "/-" + // there can't be anything more after '-' as an index + return false; + } + key_or_index = '-'; + offset = length; // will skip the loop coming right after + } + + // We either transform the first reference token to a valid json key + // or we make sure it is a valid index in an array. + for (; offset < length; offset++) { + if (pointer[offset] == '/') { + // beginning of the next key or index + break; + } + if (is_array() && (pointer[offset] < '0' || pointer[offset] > '9')) { + // the index of an array must be an integer + // we also make sure std::stoi won't discard whitespaces later + return false; + } + if (pointer[offset] == '~') { + // "~1" represents "/" + if (pointer[offset + 1] == '1') { + key_or_index += '/'; + offset++; + continue; + } + // "~0" represents "~" + if (pointer[offset + 1] == '0') { + key_or_index += '~'; + offset++; + continue; + } + } + if (pointer[offset] == '\\') { + if (pointer[offset + 1] == '\\' || pointer[offset + 1] == '"' || + (pointer[offset + 1] <= 0x1F)) { + key_or_index += pointer[offset + 1]; + offset++; + continue; + } + return false; // invalid escaped character + } + if (pointer[offset] == '\"') { + // unescaped quote character. this is an invalid case. + // lets do nothing and assume most pointers will be valid. + // it won't find any corresponding json key anyway. + // return false; + } + key_or_index += pointer[offset]; + } + + bool found = false; + if (is_object()) { + if (move_to_key(key_or_index.c_str(), key_or_index.length())) { + found = relative_move_to(pointer + offset, length - offset); + } + } else if (is_array()) { + if (key_or_index == "-") { // handling "-" case first + if (down()) { + while (next()) + ; // moving to the end of the array + // moving to the nonexistent value right after... + size_t npos; + if ((current_type == '[') || (current_type == '{')) { + // we need to jump + npos = (current_val & JSON_VALUE_MASK); + } else { + npos = + location + ((current_type == 'd' || current_type == 'l') ? 2 : 1); + } + location = npos; + current_val = doc.tape[npos]; + current_type = (current_val >> 56); + return true; // how could it fail ? + } + } else { // regular numeric index + // The index can't have a leading '0' + if (key_or_index[0] == '0' && key_or_index.length() > 1) { + return false; + } + // it cannot be empty + if (key_or_index.length() == 0) { + return false; + } + // we already checked the index contains only valid digits + uint32_t index = std::stoi(key_or_index); + if (move_to_index(index)) { + found = relative_move_to(pointer + offset, length - offset); + } + } + } + + return found; +} + +} // namespace simdjson + +#endif // SIMDJSON_INLINE_DOCUMENT_ITERATOR_H diff --git a/include/simdjson/jsonparser.h b/include/simdjson/jsonparser.h index 8f6414def..1ad6ce67f 100644 --- a/include/simdjson/jsonparser.h +++ b/include/simdjson/jsonparser.h @@ -1,3 +1,5 @@ +// TODO Remove this -- deprecated API and files + #ifndef SIMDJSON_JSONPARSER_H #define SIMDJSON_JSONPARSER_H @@ -5,4 +7,56 @@ #include "simdjson/parsedjson.h" #include "simdjson/jsonioutil.h" +namespace simdjson { + +// +// C API (json_parse and build_parsed_json) declarations +// + +inline int json_parse(const uint8_t *buf, size_t len, document::parser &parser, bool realloc_if_needed = true) noexcept { + error_code code = parser.parse(buf, len, realloc_if_needed).error; + // The deprecated json_parse API is a signal that the user plans to *use* the error code / valid + // bits in the parser instead of heeding the result code. The normal parser unsets those in + // anticipation of making the error code ephemeral. + // Here we put the code back into the parser, until we've removed this method. + parser.valid = code == SUCCESS; + parser.error = code; + return code; +} +inline int json_parse(const char *buf, size_t len, document::parser &parser, bool realloc_if_needed = true) noexcept { + return json_parse(reinterpret_cast(buf), len, parser, realloc_if_needed); +} +inline int json_parse(const std::string &s, document::parser &parser, bool realloc_if_needed = true) noexcept { + return json_parse(s.data(), s.length(), parser, realloc_if_needed); +} +inline int json_parse(const padded_string &s, document::parser &parser) noexcept { + return json_parse(s.data(), s.length(), parser, false); +} + +WARN_UNUSED static document::parser build_parsed_json(const uint8_t *buf, size_t len, bool realloc_if_needed = true) noexcept { + document::parser parser; + if (!parser.allocate_capacity(len)) { + parser.valid = false; + parser.error = MEMALLOC; + return parser; + } + json_parse(buf, len, parser, realloc_if_needed); + return parser; +} +WARN_UNUSED inline document::parser build_parsed_json(const char *buf, size_t len, bool realloc_if_needed = true) noexcept { + return build_parsed_json(reinterpret_cast(buf), len, realloc_if_needed); +} +WARN_UNUSED inline document::parser build_parsed_json(const std::string &s, bool realloc_if_needed = true) noexcept { + return build_parsed_json(s.data(), s.length(), realloc_if_needed); +} +WARN_UNUSED inline document::parser build_parsed_json(const padded_string &s) noexcept { + return build_parsed_json(s.data(), s.length(), false); +} + +// We do not want to allow implicit conversion from C string to std::string. +int json_parse(const char *buf, document::parser &parser) noexcept = delete; +document::parser build_parsed_json(const char *buf) noexcept = delete; + +} // namespace simdjson + #endif diff --git a/include/simdjson/jsonstream.h b/include/simdjson/jsonstream.h index 797ca7697..a8824cbde 100644 --- a/include/simdjson/jsonstream.h +++ b/include/simdjson/jsonstream.h @@ -8,8 +8,6 @@ #include "simdjson/isadetection.h" #include "simdjson/padded_string.h" #include "simdjson/simdjson.h" -#include "simdjson/stage1_find_marks.h" -#include "simdjson/stage2_build_tape.h" #include "jsoncharutils.h" @@ -127,7 +125,7 @@ public: inline size_t get_n_bytes_parsed() const { return n_bytes_parsed; } private: - inline const char *buf() const { return str.data() + str_start; } + inline const uint8_t *buf() const { return reinterpret_cast(str.data()) + str_start; } inline void advance(size_t offset) { str_start += offset; } @@ -144,6 +142,7 @@ private: #endif size_t n_parsed_docs{0}; size_t n_bytes_parsed{0}; + simdjson::implementation *stage_parser; #ifdef SIMDJSON_THREADS_ENABLED error_code stage1_is_ok_thread{SUCCESS}; std::thread stage_1_thread; @@ -173,7 +172,7 @@ private: * complete * document, therefore the last json buffer location is the end of the batch * */ -inline size_t find_last_json_buf_idx(const char *buf, size_t size, +inline size_t find_last_json_buf_idx(const uint8_t *buf, size_t size, const document::parser &parser) { // this function can be generally useful if (parser.n_structural_indexes == 0) @@ -221,59 +220,10 @@ inline size_t find_last_json_buf_idx(const char *buf, size_t size, return 0; } -// Everything in the following anonymous namespace should go. -// It is a hack. -namespace { - -typedef int (*stage1_functype)(const char *buf, size_t len, - document::parser &parser, bool streaming); -typedef int (*stage2_functype)(const char *buf, size_t len, - document::parser &parser, size_t &next_json); - -stage1_functype best_stage1; -stage2_functype best_stage2; - -//// TODO: generalize this set of functions. We don't want to have a copy in -/// jsonparser.cpp -void find_the_best_supported_implementation() { - uint32_t supports = simdjson::detect_supported_architectures(); -// Order from best to worst (within architecture) -#ifdef IS_X86_64 - constexpr uint32_t haswell_flags = - simdjson::instruction_set::AVX2 | simdjson::instruction_set::PCLMULQDQ | - simdjson::instruction_set::BMI1 | simdjson::instruction_set::BMI2; - constexpr uint32_t westmere_flags = - simdjson::instruction_set::SSE42 | simdjson::instruction_set::PCLMULQDQ; - if ((haswell_flags & supports) == haswell_flags) { - best_stage1 = - simdjson::find_structural_bits; - best_stage2 = simdjson::unified_machine; - return; - } - if ((westmere_flags & supports) == westmere_flags) { - best_stage1 = - simdjson::find_structural_bits; - best_stage2 = simdjson::unified_machine; - return; - } -#endif -#ifdef IS_ARM64 - if (supports & instruction_set::NEON) { - best_stage1 = simdjson::find_structural_bits; - best_stage2 = simdjson::unified_machine; - return; - } -#endif - // we throw an exception since this should not be recoverable - throw new std::runtime_error("unsupported architecture"); -} -} // anonymous namespace - template JsonStream::JsonStream(const string_container &s, size_t batchSize) : str(s), _batch_size(batchSize) { - find_the_best_supported_implementation(); } template JsonStream::~JsonStream() { @@ -312,7 +262,7 @@ int JsonStream::json_parse(document::parser &parser) { if (_batch_size == 0) { return parser.error = simdjson::UTF8_ERROR; } - auto stage1_is_ok = error_code(best_stage1(buf(), _batch_size, parser, true)); + auto stage1_is_ok = error_code(simdjson::active_implementation->stage1(buf(), _batch_size, parser, true)); if (stage1_is_ok != simdjson::SUCCESS) { return parser.error = stage1_is_ok; } @@ -348,20 +298,20 @@ int JsonStream::json_parse(document::parser &parser) { return parser.error = simdjson::UTF8_ERROR; } // let us capture read-only variables - const char *const b = buf() + last_json_buffer_loc; + const uint8_t *const b = buf() + last_json_buffer_loc; const size_t bs = _batch_size; // we call the thread on a lambda that will update // this->stage1_is_ok_thread // there is only one thread that may write to this value stage_1_thread = std::thread([this, b, bs] { - this->stage1_is_ok_thread = error_code(best_stage1(b, bs, this->parser_thread, true)); + this->stage1_is_ok_thread = error_code(simdjson::active_implementation->stage1(b, bs, this->parser_thread, true)); }); } } next_json = 0; load_next_batch = false; } // load_next_batch - int res = best_stage2(buf(), remaining(), parser, next_json); + int res = simdjson::active_implementation->stage2(buf(), remaining(), parser, next_json); if (res == simdjson::SUCCESS_AND_HAS_MORE) { n_parsed_docs++; current_buffer_loc = parser.structural_indexes[next_json]; @@ -395,7 +345,7 @@ int JsonStream::json_parse(document::parser &parser) { n_bytes_parsed += current_buffer_loc; _batch_size = (std::min)(_batch_size, remaining()); _batch_size = trimmed_length_safe_utf8((const char *)buf(), _batch_size); - auto stage1_is_ok = (error_code)best_stage1(buf(), _batch_size, parser, true); + auto stage1_is_ok = (error_code)simdjson::active_implementation->stage1(buf(), _batch_size, parser, true); if (stage1_is_ok != simdjson::SUCCESS) { return parser.on_error(stage1_is_ok); } @@ -409,7 +359,7 @@ int JsonStream::json_parse(document::parser &parser) { } load_next_batch = false; } // load_next_batch - int res = best_stage2(buf(), remaining(), parser, next_json); + int res = simdjson::active_implementation->stage2(buf(), remaining(), parser, next_json); if (likely(res == simdjson::SUCCESS_AND_HAS_MORE)) { n_parsed_docs++; current_buffer_loc = parser.structural_indexes[next_json]; diff --git a/include/simdjson/parsedjson.h b/include/simdjson/parsedjson.h index bc7fbdf82..73488242e 100644 --- a/include/simdjson/parsedjson.h +++ b/include/simdjson/parsedjson.h @@ -1,3 +1,5 @@ +// TODO Remove this -- deprecated API and files + #ifndef SIMDJSON_PARSEDJSON_H #define SIMDJSON_PARSEDJSON_H diff --git a/include/simdjson/parsedjsoniterator.h b/include/simdjson/parsedjsoniterator.h index 993a06fc6..a1ad37139 100644 --- a/include/simdjson/parsedjsoniterator.h +++ b/include/simdjson/parsedjsoniterator.h @@ -1,3 +1,5 @@ +// TODO Remove this -- deprecated API and files + #ifndef SIMDJSON_PARSEDJSONITERATOR_H #define SIMDJSON_PARSEDJSONITERATOR_H diff --git a/include/simdjson/simdjson.h b/include/simdjson/simdjson.h index ba39b2f17..1d7367715 100644 --- a/include/simdjson/simdjson.h +++ b/include/simdjson/simdjson.h @@ -17,7 +17,6 @@ #error simdjson requires a compiler compliant with the C++17 standard #endif -#include "simdjson/architecture.h" #include "simdjson/error.h" #endif // SIMDJSON_H diff --git a/include/simdjson/stage1_find_marks.h b/include/simdjson/stage1_find_marks.h deleted file mode 100755 index 50d9c45f2..000000000 --- a/include/simdjson/stage1_find_marks.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef SIMDJSON_STAGE1_FIND_MARKS_H -#define SIMDJSON_STAGE1_FIND_MARKS_H - -#include "simdjson/document.h" - -#endif diff --git a/include/simdjson/stage2_build_tape.h b/include/simdjson/stage2_build_tape.h deleted file mode 100644 index 3760c7674..000000000 --- a/include/simdjson/stage2_build_tape.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef SIMDJSON_STAGE2_BUILD_TAPE_H -#define SIMDJSON_STAGE2_BUILD_TAPE_H - -#include "simdjson/document.h" - -#endif diff --git a/singleheader/simdjson.cpp b/singleheader/simdjson.cpp index eeefe6f5b..2e959265d 100755 --- a/singleheader/simdjson.cpp +++ b/singleheader/simdjson.cpp @@ -702,8 +702,7 @@ namespace simdjson { // instruction sets. // function pointer type for json_parse -using json_parse_functype = int(const uint8_t *buf, size_t len, ParsedJson &pj, - bool realloc); +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 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f8dee4523..3f40205bc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -24,11 +24,10 @@ set(SIMDJSON_SRC_DIR $) set(SIMDJSON_SRC jsonioutil.cpp jsonminifier.cpp - jsonparser.cpp stage1_find_marks.cpp stage2_build_tape.cpp document.cpp - document_parser.cpp + implementation.cpp error.cpp ) @@ -39,6 +38,7 @@ set(SIMDJSON_SRC_HEADERS arm64/bitmask.h arm64/simd.h arm64/bitmanipulation.h + arm64/implementation.h arm64/intrinsics.h arm64/stage1_find_marks.h arm64/stage2_build_tape.h @@ -56,12 +56,14 @@ set(SIMDJSON_SRC_HEADERS generic/utf8_zwegner_algorithm.h haswell/bitmask.h haswell/bitmanipulation.h + haswell/implementation.h haswell/intrinsics.h haswell/simd.h haswell/stage1_find_marks.h haswell/stage2_build_tape.h haswell/stringparsing.h westmere/bitmanipulation.h + westmere/implementation.h westmere/intrinsics.h westmere/bitmask.h westmere/simd.h diff --git a/src/arm64/implementation.h b/src/arm64/implementation.h new file mode 100644 index 000000000..7380c5d25 --- /dev/null +++ b/src/arm64/implementation.h @@ -0,0 +1,26 @@ +#ifndef __SIMDJSON_ARM64_IMPLEMENTATION_H +#define __SIMDJSON_ARM64_IMPLEMENTATION_H + +#include "simdjson/portability.h" + +#ifdef IS_ARM64 + +#include "simdjson/implementation.h" +#include "simdjson/isadetection.h" + +namespace simdjson::arm64 { + +class implementation final : public simdjson::implementation { +public: + really_inline implementation() : simdjson::implementation("arm64", "ARM NEON", instruction_set::NEON) {} + WARN_UNUSED error_code parse(const uint8_t *buf, size_t len, document::parser &parser) const noexcept final; + WARN_UNUSED error_code stage1(const uint8_t *buf, size_t len, document::parser &parser, bool streaming) const noexcept final; + WARN_UNUSED error_code stage2(const uint8_t *buf, size_t len, document::parser &parser) const noexcept final; + WARN_UNUSED error_code stage2(const uint8_t *buf, size_t len, document::parser &parser, size_t &next_json) const noexcept final; +}; + +} // namespace simdjson::arm64 + +#endif // IS_ARM64 + +#endif // __SIMDJSON_ARM64_IMPLEMENTATION_H diff --git a/src/arm64/stage1_find_marks.h b/src/arm64/stage1_find_marks.h index 42b104f7a..1be1ea178 100644 --- a/src/arm64/stage1_find_marks.h +++ b/src/arm64/stage1_find_marks.h @@ -8,7 +8,7 @@ #include "arm64/bitmask.h" #include "arm64/simd.h" #include "arm64/bitmanipulation.h" -#include "simdjson/stage1_find_marks.h" +#include "arm64/implementation.h" namespace simdjson::arm64 { @@ -50,16 +50,12 @@ really_inline simd8 must_be_continuation(simd8 prev1, simd8 -int find_structural_bits(const uint8_t *buf, size_t len, document::parser &parser, bool streaming) { +WARN_UNUSED error_code implementation::stage1(const uint8_t *buf, size_t len, document::parser &parser, bool streaming) const noexcept { return arm64::stage1::find_structural_bits<64>(buf, len, parser, streaming); } -} // namespace simdjson +} // namespace simdjson::arm64 #endif // IS_ARM64 + #endif // SIMDJSON_ARM64_STAGE1_FIND_MARKS_H diff --git a/src/arm64/stage2_build_tape.h b/src/arm64/stage2_build_tape.h index 427ca2406..317d50185 100644 --- a/src/arm64/stage2_build_tape.h +++ b/src/arm64/stage2_build_tape.h @@ -5,7 +5,7 @@ #ifdef IS_ARM64 -#include "simdjson/stage2_build_tape.h" +#include "arm64/implementation.h" #include "arm64/stringparsing.h" #include "arm64/numberparsing.h" @@ -16,22 +16,6 @@ namespace simdjson::arm64 { } // namespace simdjson::arm64 -namespace simdjson { - -template <> -WARN_UNUSED int -unified_machine(const uint8_t *buf, size_t len, ParsedJson &pj) { - return arm64::stage2::unified_machine(buf, len, pj); -} - -template <> -WARN_UNUSED int -unified_machine(const uint8_t *buf, size_t len, ParsedJson &pj, size_t &next_json) { - return arm64::stage2::unified_machine(buf, len, pj, next_json); -} - -} // namespace simdjson - #endif // IS_ARM64 #endif // SIMDJSON_ARM64_STAGE2_BUILD_TAPE_H diff --git a/src/document.cpp b/src/document.cpp index d0935ad02..638076187 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -226,4 +226,70 @@ bool document::dump_raw_tape(std::ostream &os) const { return true; } +WARN_UNUSED +bool document::parser::set_capacity(size_t capacity) { + if (_capacity == capacity) { + return true; + } + + // Set capacity to 0 until we finish, in case there's an error + _capacity = 0; + + // + // Reallocate the document + // + if (!doc.set_capacity(capacity)) { + return false; + } + + // + // Don't allocate 0 bytes, just return. + // + if (capacity == 0) { + structural_indexes.reset(); + return true; + } + + // + // Initialize stage 1 output + // + uint32_t max_structures = ROUNDUP_N(capacity, 64) + 2 + 7; + structural_indexes.reset( new (std::nothrow) uint32_t[max_structures]); // TODO realloc + if (!structural_indexes) { + return false; + } + + _capacity = capacity; + return true; +} + +WARN_UNUSED +bool document::parser::set_max_depth(size_t max_depth) { + _max_depth = 0; + + if (max_depth == 0) { + ret_address.reset(); + containing_scope_offset.reset(); + return true; + } + + // + // Initialize stage 2 state + // + containing_scope_offset.reset(new (std::nothrow) uint32_t[max_depth]); // TODO realloc +#ifdef SIMDJSON_USE_COMPUTED_GOTO + ret_address.reset(new (std::nothrow) void *[max_depth]); +#else + ret_address.reset(new (std::nothrow) char[max_depth]); +#endif + + if (!ret_address || !containing_scope_offset) { + // Could not allocate memory + return false; + } + + _max_depth = max_depth; + return true; +} + } // namespace simdjson diff --git a/src/document_parser.cpp b/src/document_parser.cpp deleted file mode 100644 index 5a3c77748..000000000 --- a/src/document_parser.cpp +++ /dev/null @@ -1,111 +0,0 @@ -#include "simdjson/document.h" -#include "simdjson/jsonparser.h" - -namespace simdjson { - -WARN_UNUSED -error_code document::parser::init_parse(size_t len) { - if (len > capacity()) { - return error = CAPACITY; - } - // If the last doc was taken, we need to allocate a new one - if (!doc.tape) { - if (!doc.set_capacity(len)) { - return error = MEMALLOC; - } - } - return SUCCESS; -} - -WARN_UNUSED -bool document::parser::set_capacity(size_t capacity) { - if (_capacity == capacity) { - return true; - } - - // Set capacity to 0 until we finish, in case there's an error - _capacity = 0; - - // - // Reallocate the document - // - if (!doc.set_capacity(capacity)) { - return false; - } - - // - // Don't allocate 0 bytes, just return. - // - if (capacity == 0) { - structural_indexes.reset(); - return true; - } - - // - // Initialize stage 1 output - // - uint32_t max_structures = ROUNDUP_N(capacity, 64) + 2 + 7; - structural_indexes.reset( new (std::nothrow) uint32_t[max_structures]); // TODO realloc - if (!structural_indexes) { - return false; - } - - _capacity = capacity; - return true; -} - -WARN_UNUSED -bool document::parser::set_max_depth(size_t max_depth) { - _max_depth = 0; - - if (max_depth == 0) { - ret_address.reset(); - containing_scope_offset.reset(); - return true; - } - - // - // Initialize stage 2 state - // - containing_scope_offset.reset(new (std::nothrow) uint32_t[max_depth]); // TODO realloc -#ifdef SIMDJSON_USE_COMPUTED_GOTO - ret_address.reset(new (std::nothrow) void *[max_depth]); -#else - ret_address.reset(new (std::nothrow) char[max_depth]); -#endif - - if (!ret_address || !containing_scope_offset) { - // Could not allocate memory - return false; - } - - _max_depth = max_depth; - return true; -} - -void document::parser::init_stage2() { - current_string_buf_loc = doc.string_buf.get(); - current_loc = 0; - valid = false; - error = UNINITIALIZED; -} - -bool document::parser::is_valid() const { return valid; } - -int document::parser::get_error_code() const { return error; } - -std::string document::parser::get_error_message() const { - return error_message(error); -} - -WARN_UNUSED -bool document::parser::print_json(std::ostream &os) const { - return is_valid() ? doc.print_json(os) : false; -} - -WARN_UNUSED -bool document::parser::dump_raw_tape(std::ostream &os) const { - return is_valid() ? doc.dump_raw_tape(os) : false; -} - -} // namespace simdjson diff --git a/src/error.cpp b/src/error.cpp index 691323249..b9899c96c 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -10,28 +10,28 @@ const std::map error_strings = { {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'"}, + {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"}, {UNINITIALIZED, "Uninitialized"}, {EMPTY, "Empty: no JSON found"}, - {UNESCAPED_CHARS, "Within strings, some characters must be escaped, we " - "found unescaped characters"}, + {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"}, + {UNSUPPORTED_ARCHITECTURE, "simdjson does not have an implementation" + " supported by this CPU architecture (perhaps" + " it's a non-SIMD CPU?)."}, + {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(error_code code) { +const std::string &error_message(error_code code) noexcept { auto keyvalue = error_strings.find(code); if(keyvalue == error_strings.end()) { return unexpected_error_msg; diff --git a/src/generic/stage1_find_marks.h b/src/generic/stage1_find_marks.h index 9dff2d28f..2c7ab37d5 100644 --- a/src/generic/stage1_find_marks.h +++ b/src/generic/stage1_find_marks.h @@ -391,7 +391,7 @@ really_inline void json_structural_scanner::scan(const uint8_t *buf, const size_ // The caller should still ensure that the input is valid UTF-8. If you are processing substrings, // you may want to call on a function like trimmed_length_safe_utf8. template -int find_structural_bits(const uint8_t *buf, size_t len, document::parser &parser, bool streaming) { +error_code find_structural_bits(const uint8_t *buf, size_t len, document::parser &parser, bool streaming) { if (unlikely(len > parser.capacity())) { return CAPACITY; } diff --git a/src/generic/stage2_build_tape.h b/src/generic/stage2_build_tape.h index fadaad577..71044c4c8 100644 --- a/src/generic/stage2_build_tape.h +++ b/src/generic/stage2_build_tape.h @@ -273,15 +273,16 @@ struct structural_parser { #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 int -unified_machine(const uint8_t *buf, size_t len, document::parser &doc_parser) { - static constexpr unified_machine_addresses addresses = INIT_ADDRESSES(); - structural_parser parser(buf, len, doc_parser); - int result = parser.start(addresses.finish); +WARN_UNUSED error_code implementation::stage2(const uint8_t *buf, size_t len, document::parser &doc_parser) const noexcept { + static constexpr stage2::unified_machine_addresses addresses = INIT_ADDRESSES(); + stage2::structural_parser parser(buf, len, doc_parser); + error_code result = parser.start(addresses.finish); if (result) { return result; } // @@ -394,4 +395,10 @@ error: return parser.error(); } -} // namespace stage2 \ No newline at end of file +WARN_UNUSED error_code implementation::parse(const uint8_t *buf, size_t len, document::parser &doc_parser) const noexcept { + error_code code = stage1(buf, len, doc_parser, false); + if (!code) { + code = stage2(buf, len, doc_parser); + } + return code; +} diff --git a/src/generic/stage2_streaming_build_tape.h b/src/generic/stage2_streaming_build_tape.h index 023f2bbeb..d72161298 100755 --- a/src/generic/stage2_streaming_build_tape.h +++ b/src/generic/stage2_streaming_build_tape.h @@ -1,7 +1,7 @@ namespace stage2 { struct streaming_structural_parser: structural_parser { - really_inline streaming_structural_parser(const uint8_t *_buf, size_t _len, ParsedJson &_pj, size_t _i) : structural_parser(_buf, _len, _pj, _i) {} + really_inline streaming_structural_parser(const uint8_t *_buf, size_t _len, ParsedJson &_doc_parser, size_t _i) : structural_parser(_buf, _len, _doc_parser, _i) {} // override to add streaming WARN_UNUSED really_inline error_code start(ret_address finish_parser) { @@ -33,15 +33,16 @@ struct streaming_structural_parser: structural_parser { } }; +} // namespace stage2 + /************ * 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, size_t &next_json) { - static constexpr unified_machine_addresses addresses = INIT_ADDRESSES(); - streaming_structural_parser parser(buf, len, pj, next_json); - int result = parser.start(addresses.finish); +WARN_UNUSED error_code implementation::stage2(const uint8_t *buf, size_t len, document::parser &doc_parser, size_t &next_json) const noexcept { + static constexpr stage2::unified_machine_addresses addresses = INIT_ADDRESSES(); + stage2::streaming_structural_parser parser(buf, len, doc_parser, next_json); + error_code result = parser.start(addresses.finish); if (result) { return result; } // // Read first value @@ -153,5 +154,3 @@ finish: error: return parser.error(); } - -} // namespace stage2 \ No newline at end of file diff --git a/src/haswell/implementation.h b/src/haswell/implementation.h new file mode 100644 index 000000000..55b555998 --- /dev/null +++ b/src/haswell/implementation.h @@ -0,0 +1,30 @@ +#ifndef __SIMDJSON_HASWELL_IMPLEMENTATION_H +#define __SIMDJSON_HASWELL_IMPLEMENTATION_H + +#include "simdjson/portability.h" + +#ifdef IS_X86_64 + +#include "simdjson/implementation.h" +#include "simdjson/isadetection.h" + +namespace simdjson::haswell { + +class implementation final : public simdjson::implementation { +public: + really_inline implementation() : simdjson::implementation( + "haswell", + "Intel/AMD AVX2", + instruction_set::AVX2 | instruction_set::PCLMULQDQ | instruction_set::BMI1 | instruction_set::BMI2 + ) {} + WARN_UNUSED error_code parse(const uint8_t *buf, size_t len, document::parser &parser) const noexcept final; + WARN_UNUSED error_code stage1(const uint8_t *buf, size_t len, document::parser &parser, bool streaming) const noexcept final; + WARN_UNUSED error_code stage2(const uint8_t *buf, size_t len, document::parser &parser) const noexcept final; + WARN_UNUSED error_code stage2(const uint8_t *buf, size_t len, document::parser &parser, size_t &next_json) const noexcept final; +}; + +} // namespace simdjson::haswell + +#endif // IS_X86_64 + +#endif // __SIMDJSON_HASWELL_IMPLEMENTATION_H \ No newline at end of file diff --git a/src/haswell/stage1_find_marks.h b/src/haswell/stage1_find_marks.h index d4544e050..4ed0e2d6d 100644 --- a/src/haswell/stage1_find_marks.h +++ b/src/haswell/stage1_find_marks.h @@ -8,7 +8,7 @@ #include "haswell/bitmask.h" #include "haswell/simd.h" #include "haswell/bitmanipulation.h" -#include "simdjson/stage1_find_marks.h" +#include "haswell/implementation.h" TARGET_HASWELL namespace simdjson::haswell { @@ -48,18 +48,11 @@ really_inline simd8 must_be_continuation(simd8 prev1, simd8 -int find_structural_bits(const uint8_t *buf, size_t len, document::parser &parser, bool streaming) { +WARN_UNUSED error_code implementation::stage1(const uint8_t *buf, size_t len, document::parser &parser, bool streaming) const noexcept { return haswell::stage1::find_structural_bits<128>(buf, len, parser, streaming); } -} // namespace simdjson +} // namespace simdjson::haswell UNTARGET_REGION #endif // IS_X86_64 diff --git a/src/haswell/stage2_build_tape.h b/src/haswell/stage2_build_tape.h index 5f213bcb3..4dc8d977e 100644 --- a/src/haswell/stage2_build_tape.h +++ b/src/haswell/stage2_build_tape.h @@ -5,7 +5,7 @@ #ifdef IS_X86_64 -#include "simdjson/stage2_build_tape.h" +#include "haswell/implementation.h" #include "haswell/stringparsing.h" #include "haswell/numberparsing.h" @@ -15,24 +15,6 @@ namespace simdjson::haswell { #include "generic/stage2_build_tape.h" #include "generic/stage2_streaming_build_tape.h" -} // 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::stage2::unified_machine(buf, len, pj); -} - -template <> -WARN_UNUSED int -unified_machine(const uint8_t *buf, size_t len, ParsedJson &pj, UNUSED size_t &next_json) { - return haswell::stage2::unified_machine(buf, len, pj, next_json); -} - } // namespace simdjson UNTARGET_REGION diff --git a/src/implementation.cpp b/src/implementation.cpp new file mode 100644 index 000000000..cbba5dee5 --- /dev/null +++ b/src/implementation.cpp @@ -0,0 +1,83 @@ +#include "simdjson/portability.h" +#include "simdjson/isadetection.h" +#include "simdjson/implementation.h" +#include + +// Static array of known implementations. We're hoping these get baked into the executable +// without requiring a static initializer. + +#ifdef IS_X86_64 + +#include "haswell/implementation.h" +#include "westmere/implementation.h" + +namespace simdjson { + const haswell::implementation haswell_singleton{}; + const westmere::implementation westmere_singleton{}; + constexpr const std::initializer_list available_implementation_pointers { &haswell_singleton, &westmere_singleton }; +} + +#endif + +#ifdef IS_ARM64 + +#include "arm64/implementation.h" + +namespace simdjson { + const arm64::implementation arm64_singleton{}; + constexpr const std::initializer_list available_implementation_pointers { &arm64_singleton }; +} + +#endif + +namespace simdjson { + +// So we can return UNSUPPORTED_ARCHITECTURE from the parser when there is no support +class unsupported_implementation final : public implementation { +public: + WARN_UNUSED virtual error_code parse(const uint8_t *, size_t, document::parser &) const noexcept final { + return UNSUPPORTED_ARCHITECTURE; + } + WARN_UNUSED error_code stage1(const uint8_t *, size_t, document::parser &, bool) const noexcept final { + return UNSUPPORTED_ARCHITECTURE; + } + WARN_UNUSED error_code stage2(const uint8_t *, size_t, document::parser &) const noexcept final { + return UNSUPPORTED_ARCHITECTURE; + } + WARN_UNUSED error_code stage2(const uint8_t *, size_t, document::parser &, size_t &) const noexcept final { + return UNSUPPORTED_ARCHITECTURE; + } + + unsupported_implementation() : implementation("unsupported", "Unsupported CPU (no detected SIMD instructions)", 0) {} +}; + +const unsupported_implementation unsupported_singleton{}; + +namespace internal { + +size_t available_implementation_list::size() const noexcept { + return available_implementation_pointers.size(); +} +const implementation * const *available_implementation_list::begin() const noexcept { + return available_implementation_pointers.begin(); +} +const implementation * const *available_implementation_list::end() const noexcept { + return available_implementation_pointers.end(); +} +const implementation *available_implementation_list::detect_best_supported() const noexcept { + // They are prelisted in priority order, so we just go down the list + uint32_t supported_instruction_sets = detect_supported_architectures(); + for (const implementation *impl : available_implementation_pointers) { + uint32_t required_instruction_sets = impl->required_instruction_sets(); + if ((supported_instruction_sets & required_instruction_sets) == required_instruction_sets) { return impl; } + } + return &unsupported_singleton; +} + +const implementation *detect_best_supported_implementation_on_first_use::set_best() const noexcept { + return active_implementation = available_implementations.detect_best_supported(); +} + +} // namespace simdjson::internal + +} // namespace simdjson diff --git a/src/jsonparser.cpp b/src/jsonparser.cpp deleted file mode 100644 index a45c5b619..000000000 --- a/src/jsonparser.cpp +++ /dev/null @@ -1,92 +0,0 @@ -#include "simdjson/jsonparser.h" -#include "simdjson/isadetection.h" -#include "simdjson/portability.h" -#include "simdjson/simdjson.h" -#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, document::parser &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, document::parser &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, document::parser &pj, bool realloc) { - return json_parse_ptr.load(std::memory_order_relaxed)(reinterpret_cast(buf), len, pj, - realloc); -} - -architecture find_best_supported_architecture() { - 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 (supports & instruction_set::NEON) - return architecture::ARM64; - - return architecture::UNSUPPORTED; -} - -architecture parse_architecture(char *arch) { - if (!strcmp(arch, "HASWELL")) { return architecture::HASWELL; } - if (!strcmp(arch, "WESTMERE")) { return architecture::WESTMERE; } - if (!strcmp(arch, "ARM64")) { return architecture::ARM64; } - return architecture::UNSUPPORTED; -} - -// Responsible to select the best json_parse implementation -int json_parse_dispatch(const uint8_t *buf, size_t len, document::parser &pj, bool realloc) { - architecture best_implementation = find_best_supported_architecture(); - // 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: - // The processor is not supported by simdjson. - 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 -document::parser build_parsed_json(const uint8_t *buf, size_t len, bool realloc) { - document::parser parser; - bool ok = parser.allocate_capacity(len); - if (ok) { - json_parse(buf, len, parser, realloc); - } - return parser; -} -} // namespace simdjson diff --git a/src/westmere/implementation.h b/src/westmere/implementation.h new file mode 100644 index 000000000..b6b8c681a --- /dev/null +++ b/src/westmere/implementation.h @@ -0,0 +1,26 @@ +#ifndef __SIMDJSON_WESTMERE_IMPLEMENTATION_H +#define __SIMDJSON_WESTMERE_IMPLEMENTATION_H + +#include "simdjson/portability.h" + +#ifdef IS_X86_64 + +#include "simdjson/implementation.h" +#include "simdjson/isadetection.h" + +namespace simdjson::westmere { + +class implementation final : public simdjson::implementation { +public: + really_inline implementation() : simdjson::implementation("westmere", "Intel/AMD SSE4.2", instruction_set::SSE42 | instruction_set::PCLMULQDQ) {} + WARN_UNUSED error_code parse(const uint8_t *buf, size_t len, document::parser &parser) const noexcept final; + WARN_UNUSED error_code stage1(const uint8_t *buf, size_t len, document::parser &parser, bool streaming) const noexcept final; + WARN_UNUSED error_code stage2(const uint8_t *buf, size_t len, document::parser &parser) const noexcept final; + WARN_UNUSED error_code stage2(const uint8_t *buf, size_t len, document::parser &parser, size_t &next_json) const noexcept final; +}; + +} // namespace simdjson::westmere + +#endif // IS_X86_64 + +#endif // __SIMDJSON_WESTMERE_IMPLEMENTATION_H \ No newline at end of file diff --git a/src/westmere/stage1_find_marks.h b/src/westmere/stage1_find_marks.h index 4bd58d980..46a5ab79f 100644 --- a/src/westmere/stage1_find_marks.h +++ b/src/westmere/stage1_find_marks.h @@ -8,7 +8,7 @@ #include "westmere/bitmask.h" #include "westmere/simd.h" #include "westmere/bitmanipulation.h" -#include "simdjson/stage1_find_marks.h" +#include "westmere/implementation.h" TARGET_WESTMERE namespace simdjson::westmere { @@ -50,18 +50,11 @@ really_inline simd8 must_be_continuation(simd8 prev1, simd8 -int find_structural_bits(const uint8_t *buf, size_t len, document::parser &parser, bool streaming) { +WARN_UNUSED error_code implementation::stage1(const uint8_t *buf, size_t len, document::parser &parser, bool streaming) const noexcept { return westmere::stage1::find_structural_bits<64>(buf, len, parser, streaming); } -} // namespace simdjson +} // namespace simdjson::westmere UNTARGET_REGION #endif // IS_X86_64 diff --git a/src/westmere/stage2_build_tape.h b/src/westmere/stage2_build_tape.h index adc0765d4..14294f463 100644 --- a/src/westmere/stage2_build_tape.h +++ b/src/westmere/stage2_build_tape.h @@ -5,7 +5,7 @@ #ifdef IS_X86_64 -#include "simdjson/stage2_build_tape.h" +#include "westmere/implementation.h" #include "westmere/stringparsing.h" #include "westmere/numberparsing.h" @@ -18,25 +18,6 @@ namespace simdjson::westmere { } // 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::stage2::unified_machine(buf, len, pj); -} - -template <> -WARN_UNUSED int -unified_machine(const uint8_t *buf, size_t len, ParsedJson &pj, size_t &next_json) { - return westmere::stage2::unified_machine(buf, len, pj, next_json); -} - - -} // namespace simdjson -UNTARGET_REGION - #endif // IS_X86_64 #endif // SIMDJSON_WESTMERE_STAGE2_BUILD_TAPE_H diff --git a/tests/basictests.cpp b/tests/basictests.cpp index 32246b240..b25d82a22 100644 --- a/tests/basictests.cpp +++ b/tests/basictests.cpp @@ -40,7 +40,7 @@ bool number_test_small_integers() { fflush(NULL); auto ok1 = json_parse(buf, n, parser); if (ok1 != 0 || !parser.is_valid()) { - printf("Could not parse: %s.\n", buf); + printf("Could not parse '%s': %s\n", buf, simdjson::error_message(ok1).c_str()); return false; } simdjson::document::iterator iter(parser);